/**
   * Generates a unique and non-repeating primary key for specified dbEntity.
   *
   * <p>This implementation is naive since it does not lock the database rows when executing select
   * and subsequent update. Adapter-specific implementations are more robust.
   *
   * @since 3.0
   */
  public Object generatePk(DataNode node, DbAttribute pk) throws Exception {

    DbEntity entity = (DbEntity) pk.getEntity();

    switch (pk.getType()) {
      case Types.BINARY:
      case Types.VARBINARY:
        return IDUtil.pseudoUniqueSecureByteSequence(pk.getMaxLength());
    }

    DbKeyGenerator pkGenerator = entity.getPrimaryKeyGenerator();
    long cacheSize;
    if (pkGenerator != null && pkGenerator.getKeyCacheSize() != null)
      cacheSize = pkGenerator.getKeyCacheSize().intValue();
    else cacheSize = pkCacheSize;

    long value;

    // if no caching, always generate fresh
    if (cacheSize <= 1) {
      value = longPkFromDatabase(node, entity);
    } else {
      synchronized (pkCache) {
        LongPkRange r = pkCache.get(entity.getName());

        if (r == null) {
          // created exhausted LongPkRange
          r = new LongPkRange(1l, 0l);
          pkCache.put(entity.getName(), r);
        }

        if (r.isExhausted()) {
          long val = longPkFromDatabase(node, entity);
          r.reset(val, val + cacheSize - 1);
        }

        value = r.getNextPrimaryKey();
      }
    }

    if (pk.getType() == Types.BIGINT) {
      return Long.valueOf(value);
    } else {
      // leaving it up to the user to ensure that PK does not exceed max int...
      return Integer.valueOf((int) value);
    }
  }
  @Override
  protected ParameterBinding[] createBindings() {
    List<DbAttribute> dbAttributes = query.getDbAttributes();
    int len = dbAttributes.size();

    ParameterBinding[] bindings = new ParameterBinding[len];

    for (int i = 0; i < len; i++) {
      DbAttribute attribute = dbAttributes.get(i);

      String typeName = TypesMapping.getJavaBySqlType(attribute.getType());
      ExtendedType extendedType = adapter.getExtendedTypes().getRegisteredType(typeName);
      bindings[i] = new ParameterBinding(attribute, extendedType);
    }

    return bindings;
  }
Exemplo n.º 3
0
  /**
   * Used by subclasses to resolve deferred values on demand. This is useful when a certain value
   * comes from a generated key of another master object.
   */
  protected Object getValue(Map<String, Object> valueMap, DbAttribute attribute) {

    Object value = valueMap.get(attribute.getName());

    // if a value is a Factory, resolve it here...
    // slight chance that a normal value will implement Factory interface???
    if (value instanceof Factory) {
      value = ((Factory) value).create();
      valueMap.put(attribute.getName(), value);

      // update replacement id
      if (attribute.isPrimaryKey()) {
        // sanity check
        if (value == null) {
          String name = attribute.getEntity() != null ? attribute.getEntity().getName() : "<null>";
          throw new CayenneRuntimeException(
              "Failed to generate PK: " + name + "." + attribute.getName());
        }

        ObjectId id = getObjectId();
        if (id != null) {
          // always override with fresh value as this is what's in the
          // DB
          id.getReplacementIdMap().put(attribute.getName(), value);
        }
      }
    }

    return value;
  }
  public List<String> createSql(DbEntity entity, DbAttribute column) {
    SQLParameterBinding value = get(entity, column);
    if (value == null) {
      return Collections.emptyList();
    }

    // TODO: change things so it is possible to use prepared statements here
    return Collections.singletonList(
        "UPDATE "
            + entity.getFullyQualifiedName()
            + " SET "
            + column.getName()
            + "='"
            + value.getValue()
            + "' WHERE "
            + column.getName()
            + " IS NULL");
  }
  /** Binds BatchQuery parameters to the PreparedStatement. */
  @Override
  public void bindParameters(PreparedStatement statement, BatchQuery query)
      throws SQLException, Exception {

    List<DbAttribute> dbAttributes = query.getDbAttributes();
    int attributeCount = dbAttributes.size();

    // i - attribute position in the query
    // j - PreparedStatement parameter position (starts with "1")
    for (int i = 0, j = 1; i < attributeCount; i++) {
      Object value = query.getValue(i);
      DbAttribute attribute = dbAttributes.get(i);
      int type = attribute.getType();

      // TODO: (Andrus) This works as long as there is no LOBs in qualifier
      if (isUpdateableColumn(value, type)) {
        adapter.bindParameter(statement, value, j, type, attribute.getScale());

        j++;
      }
    }
  }
  @Override
  protected ParameterBinding[] doUpdateBindings(BatchQueryRow row) {

    int len = bindings.length;

    for (int i = 0, j = 1; i < len; i++) {

      ParameterBinding b = bindings[i];

      Object value = row.getValue(i);
      DbAttribute attribute = b.getAttribute();
      int type = attribute.getType();

      // TODO: (Andrus) This works as long as there is no LOBs in
      // qualifier
      if (isUpdateableColumn(value, type)) {
        b.include(j++, value);
      } else {
        b.exclude();
      }
    }

    return bindings;
  }
  /**
   * Appends parameter placeholder for the value of the column being updated. If requested, performs
   * special handling on LOB columns.
   */
  protected void appendUpdatedParameter(StringBuffer buf, DbAttribute dbAttribute, Object value) {

    int type = dbAttribute.getType();

    if (isUpdateableColumn(value, type)) {
      buf.append('?');
    } else {
      if (type == Types.CLOB) {
        buf.append(newClobFunction);
      } else if (type == Types.BLOB) {
        buf.append(newBlobFunction);
      } else {
        throw new CayenneRuntimeException(
            "Unknown LOB column type: "
                + type
                + "("
                + TypesMapping.getSqlNameByType(type)
                + "). Query buffer: "
                + buf);
      }
    }
  }
 private String createKey(DbEntity entity, DbAttribute attribute) {
   return (entity.getFullyQualifiedName() + "." + attribute.getName()).toUpperCase();
 }
 public void set(DbEntity entity, DbAttribute column, Object value, int type) {
   values.put(
       createKey(entity, column),
       new SQLParameterBinding(value, type, column.getAttributePrecision()));
 }
  /**
   * Customizes table creating procedure for PostgreSQL. One difference with generic implementation
   * is that "bytea" type has no explicit length unlike similar binary types in other databases.
   *
   * @since 1.0.2
   */
  @Override
  public String createTable(DbEntity ent) {
    boolean status;
    if (ent.getDataMap() != null && ent.getDataMap().isQuotingSQLIdentifiers()) {
      status = true;
    } else {
      status = false;
    }
    QuotingStrategy context = getQuotingStrategy(status);
    StringBuilder buf = new StringBuilder();
    buf.append("CREATE TABLE ");

    buf.append(context.quoteFullyQualifiedName(ent));

    buf.append(" (");

    // columns
    Iterator<DbAttribute> it = ent.getAttributes().iterator();
    boolean first = true;
    while (it.hasNext()) {
      if (first) first = false;
      else buf.append(", ");

      DbAttribute at = it.next();

      // attribute may not be fully valid, do a simple check
      if (at.getType() == TypesMapping.NOT_DEFINED) {
        throw new CayenneRuntimeException(
            "Undefined type for attribute '"
                + ent.getFullyQualifiedName()
                + "."
                + at.getName()
                + "'.");
      }

      String[] types = externalTypesForJdbcType(at.getType());
      if (types == null || types.length == 0) {
        throw new CayenneRuntimeException(
            "Undefined type for attribute '"
                + ent.getFullyQualifiedName()
                + "."
                + at.getName()
                + "': "
                + at.getType());
      }

      String type = types[0];
      buf.append(context.quoteString(at.getName())).append(' ').append(type);

      // append size and precision (if applicable)
      if (typeSupportsLength(at.getType())) {
        int len = at.getMaxLength();
        int scale =
            (TypesMapping.isDecimal(at.getType()) && at.getType() != Types.FLOAT) // Postgress
                // don't
                // support
                // notations
                // float(a,
                // b)
                ? at.getScale()
                : -1;

        // sanity check
        if (scale > len) {
          scale = -1;
        }

        if (len > 0) {
          buf.append('(').append(len);

          if (scale >= 0) {
            buf.append(", ").append(scale);
          }

          buf.append(')');
        }
      }

      if (at.isMandatory()) {
        buf.append(" NOT NULL");
      } else {
        buf.append(" NULL");
      }
    }

    // primary key clause
    Iterator<DbAttribute> pkit = ent.getPrimaryKeys().iterator();
    if (pkit.hasNext()) {
      if (first) first = false;
      else buf.append(", ");

      buf.append("PRIMARY KEY (");
      boolean firstPk = true;
      while (pkit.hasNext()) {
        if (firstPk) firstPk = false;
        else buf.append(", ");

        DbAttribute at = pkit.next();
        buf.append(context.quoteString(at.getName()));
      }
      buf.append(')');
    }
    buf.append(')');
    return buf.toString();
  }
  public void testForeignKey() throws Exception {
    dropTableIfPresent("NEW_TABLE");
    dropTableIfPresent("NEW_TABLE2");

    assertTokensAndExecute(0, 0);

    DbEntity dbEntity1 = new DbEntity("NEW_TABLE");

    DbAttribute e1col1 = new DbAttribute("ID", Types.INTEGER, dbEntity1);
    e1col1.setMandatory(true);
    e1col1.setPrimaryKey(true);
    dbEntity1.addAttribute(e1col1);

    DbAttribute e1col2 = new DbAttribute("NAME", Types.VARCHAR, dbEntity1);
    e1col2.setMaxLength(10);
    e1col2.setMandatory(false);
    dbEntity1.addAttribute(e1col2);

    map.addDbEntity(dbEntity1);

    DbEntity dbEntity2 = new DbEntity("NEW_TABLE2");
    DbAttribute e2col1 = new DbAttribute("ID", Types.INTEGER, dbEntity2);
    e2col1.setMandatory(true);
    e2col1.setPrimaryKey(true);
    dbEntity2.addAttribute(e2col1);
    DbAttribute e2col2 = new DbAttribute("FK", Types.INTEGER, dbEntity2);
    dbEntity2.addAttribute(e2col2);
    DbAttribute e2col3 = new DbAttribute("NAME", Types.VARCHAR, dbEntity2);
    e2col3.setMaxLength(10);
    dbEntity2.addAttribute(e2col3);

    map.addDbEntity(dbEntity2);

    // create db relationships
    DbRelationship rel1To2 = new DbRelationship("rel1To2");
    rel1To2.setSourceEntity(dbEntity1);
    rel1To2.setTargetEntity(dbEntity2);
    rel1To2.setToMany(true);
    rel1To2.addJoin(new DbJoin(rel1To2, e1col1.getName(), e2col2.getName()));
    dbEntity1.addRelationship(rel1To2);
    DbRelationship rel2To1 = new DbRelationship("rel2To1");
    rel2To1.setSourceEntity(dbEntity2);
    rel2To1.setTargetEntity(dbEntity1);
    rel2To1.setToMany(false);
    rel2To1.addJoin(new DbJoin(rel2To1, e2col2.getName(), e1col1.getName()));
    dbEntity2.addRelationship(rel2To1);
    assertSame(rel1To2, rel2To1.getReverseRelationship());
    assertSame(rel2To1, rel1To2.getReverseRelationship());

    assertTokensAndExecute(4, 0);
    assertTokensAndExecute(0, 0);

    // create ObjEntities
    ObjEntity objEntity1 = new ObjEntity("NewTable");
    objEntity1.setDbEntity(dbEntity1);
    ObjAttribute oatr1 = new ObjAttribute("name");
    oatr1.setDbAttributePath(e1col2.getName());
    oatr1.setType("java.lang.String");
    objEntity1.addAttribute(oatr1);
    map.addObjEntity(objEntity1);
    ObjEntity objEntity2 = new ObjEntity("NewTable2");
    objEntity2.setDbEntity(dbEntity2);
    ObjAttribute o2a1 = new ObjAttribute("name");
    o2a1.setDbAttributePath(e2col3.getName());
    o2a1.setType("java.lang.String");
    objEntity2.addAttribute(o2a1);
    map.addObjEntity(objEntity2);

    // create ObjRelationships
    assertEquals(0, objEntity1.getRelationships().size());
    assertEquals(0, objEntity2.getRelationships().size());
    ObjRelationship objRel1To2 = new ObjRelationship("objRel1To2");
    objRel1To2.addDbRelationship(rel1To2);
    objRel1To2.setSourceEntity(objEntity1);
    objRel1To2.setTargetEntity(objEntity2);
    objEntity1.addRelationship(objRel1To2);
    ObjRelationship objRel2To1 = new ObjRelationship("objRel2To1");
    objRel2To1.addDbRelationship(rel2To1);
    objRel2To1.setSourceEntity(objEntity2);
    objRel2To1.setTargetEntity(objEntity1);
    objEntity2.addRelationship(objRel2To1);
    assertEquals(1, objEntity1.getRelationships().size());
    assertEquals(1, objEntity2.getRelationships().size());
    assertSame(objRel1To2, objRel2To1.getReverseRelationship());
    assertSame(objRel2To1, objRel1To2.getReverseRelationship());

    // remove relationship and fk from model, merge to db and read to model
    dbEntity2.removeRelationship(rel2To1.getName());
    dbEntity1.removeRelationship(rel1To2.getName());
    dbEntity2.removeAttribute(e2col2.getName());
    List<MergerToken> tokens = createMergeTokens();
    assertTokens(tokens, 2, 1);
    for (MergerToken token : tokens) {
      if (token.getDirection().isToDb()) {
        execute(token);
      }
    }
    assertTokensAndExecute(0, 0);
    dbEntity2.addRelationship(rel2To1);
    dbEntity1.addRelationship(rel1To2);
    dbEntity2.addAttribute(e2col2);

    // try do use the merger to remove the relationship in the model
    tokens = createMergeTokens();
    assertTokens(tokens, 2, 0);
    // TODO: reversing the following two tokens should also reverse the order
    MergerToken token0 = tokens.get(0).createReverse(mergerFactory());
    MergerToken token1 = tokens.get(1).createReverse(mergerFactory());
    if (!(token0 instanceof DropRelationshipToModel && token1 instanceof DropColumnToModel
        || token1 instanceof DropRelationshipToModel && token0 instanceof DropColumnToModel)) {
      fail();
    }
    execute(token0);
    execute(token1);

    // check after merging
    assertNull(dbEntity2.getAttribute(e2col2.getName()));
    assertEquals(0, dbEntity1.getRelationships().size());
    assertEquals(0, dbEntity2.getRelationships().size());
    assertEquals(0, objEntity1.getRelationships().size());
    assertEquals(0, objEntity2.getRelationships().size());

    // clear up
    dbEntity1.removeRelationship(rel1To2.getName());
    dbEntity2.removeRelationship(rel2To1.getName());
    map.removeObjEntity(objEntity1.getName(), true);
    map.removeDbEntity(dbEntity1.getName(), true);
    map.removeObjEntity(objEntity2.getName(), true);
    map.removeDbEntity(dbEntity2.getName(), true);
    resolver.refreshMappingCache();
    assertNull(map.getObjEntity(objEntity1.getName()));
    assertNull(map.getDbEntity(dbEntity1.getName()));
    assertNull(map.getObjEntity(objEntity2.getName()));
    assertNull(map.getDbEntity(dbEntity2.getName()));
    assertFalse(map.getDbEntities().contains(dbEntity1));
    assertFalse(map.getDbEntities().contains(dbEntity2));

    assertTokensAndExecute(2, 0);
    assertTokensAndExecute(0, 0);
  }