Search in sources :

Example 1 with Column

use of com.google.cloud.teleport.spanner.ddl.Column in project DataflowTemplates by GoogleCloudPlatform.

the class AvroRecordConverter method apply.

public Mutation apply(GenericRecord record) {
    Schema schema = record.getSchema();
    List<Schema.Field> fields = schema.getFields();
    Mutation.WriteBuilder builder = Mutation.newInsertOrUpdateBuilder(table.name());
    for (Schema.Field field : fields) {
        String fieldName = field.name();
        Column column = table.column(fieldName);
        if (column == null) {
            throw new IllegalArgumentException(String.format("Cannot find corresponding column for field %s in table %s schema %s", fieldName, table.prettyPrint(), schema.toString(true)));
        }
        if (column.isGenerated()) {
            // Spanner will compute generated column values automatically.
            continue;
        }
        Schema avroFieldSchema = field.schema();
        if (avroFieldSchema.getType() == Schema.Type.UNION) {
            Schema unpacked = AvroUtil.unpackNullable(avroFieldSchema);
            if (unpacked != null) {
                avroFieldSchema = unpacked;
            }
        }
        LogicalType logicalType = LogicalTypes.fromSchema(avroFieldSchema);
        Schema.Type avroType = avroFieldSchema.getType();
        switch(column.type().getCode()) {
            case BOOL:
            case PG_BOOL:
                builder.set(column.name()).to(readBool(record, avroType, fieldName).orElse(null));
                break;
            case INT64:
            case PG_INT8:
                builder.set(column.name()).to(readInt64(record, avroType, fieldName).orElse(null));
                break;
            case FLOAT64:
            case PG_FLOAT8:
                builder.set(column.name()).to(readFloat64(record, avroType, fieldName).orElse(null));
                break;
            case STRING:
            case PG_VARCHAR:
            case PG_TEXT:
            case JSON:
                builder.set(column.name()).to(readString(record, avroType, fieldName).orElse(null));
                break;
            case BYTES:
            case PG_BYTEA:
                builder.set(column.name()).to(readBytes(record, avroType, fieldName).orElse(null));
                break;
            case TIMESTAMP:
            case PG_TIMESTAMPTZ:
                builder.set(column.name()).to(readTimestamp(record, avroType, logicalType, fieldName).orElse(null));
                break;
            case DATE:
            case PG_DATE:
                builder.set(column.name()).to(readDate(record, avroType, logicalType, fieldName).orElse(null));
                break;
            case NUMERIC:
                builder.set(column.name()).to(readNumeric(record, avroType, fieldName).orElse(null));
                break;
            case PG_NUMERIC:
                builder.set(column.name()).to(Value.pgNumeric(readPgNumeric(record, avroType, fieldName).orElse(null)));
                break;
            case ARRAY:
            case PG_ARRAY:
                {
                    Schema arraySchema = avroFieldSchema.getElementType();
                    if (arraySchema.getType() == Schema.Type.UNION) {
                        Schema unpacked = AvroUtil.unpackNullable(arraySchema);
                        if (unpacked != null) {
                            arraySchema = unpacked;
                        }
                    }
                    LogicalType arrayLogicalType = LogicalTypes.fromSchema(arraySchema);
                    Schema.Type arrayType = arraySchema.getType();
                    switch(column.type().getArrayElementType().getCode()) {
                        case BOOL:
                        case PG_BOOL:
                            builder.set(column.name()).toBoolArray(readBoolArray(record, arrayType, fieldName).orElse(null));
                            break;
                        case INT64:
                        case PG_INT8:
                            builder.set(column.name()).toInt64Array(readInt64Array(record, arrayType, fieldName).orElse(null));
                            break;
                        case FLOAT64:
                        case PG_FLOAT8:
                            builder.set(column.name()).toFloat64Array(readFloat64Array(record, arrayType, fieldName).orElse(null));
                            break;
                        case STRING:
                        case PG_VARCHAR:
                        case PG_TEXT:
                        case JSON:
                            builder.set(column.name()).toStringArray(readStringArray(record, arrayType, fieldName).orElse(null));
                            break;
                        case BYTES:
                        case PG_BYTEA:
                            builder.set(column.name()).toBytesArray(readBytesArray(record, arrayType, fieldName).orElse(null));
                            break;
                        case TIMESTAMP:
                        case PG_TIMESTAMPTZ:
                            builder.set(column.name()).toTimestampArray(readTimestampArray(record, arrayType, arrayLogicalType, fieldName).orElse(null));
                            break;
                        case DATE:
                        case PG_DATE:
                            builder.set(column.name()).toDateArray(readDateArray(record, arrayType, fieldName).orElse(null));
                            break;
                        case NUMERIC:
                            builder.set(column.name()).toStringArray(readNumericArray(record, arrayType, fieldName).orElse(null));
                            break;
                        case PG_NUMERIC:
                            builder.set(column.name()).toPgNumericArray(readPgNumericArray(record, arrayType, fieldName).orElse(null));
                            break;
                        default:
                            throw new IllegalArgumentException(String.format("Cannot convert field %s in schema %s table %s", fieldName, schema.toString(true), table.prettyPrint()));
                    }
                    break;
                }
            default:
                throw new IllegalArgumentException(String.format("Cannot convert field %s in schema %s table %s", fieldName, schema.toString(true), table.prettyPrint()));
        }
    }
    return builder.build();
}
Also used : LogicalType(org.apache.avro.LogicalType) Column(com.google.cloud.teleport.spanner.ddl.Column) Schema(org.apache.avro.Schema) LogicalType(org.apache.avro.LogicalType) Mutation(com.google.cloud.spanner.Mutation)

Example 2 with Column

use of com.google.cloud.teleport.spanner.ddl.Column in project DataflowTemplates by GoogleCloudPlatform.

the class AvroSchemaToDdlConverter method toTable.

public Table toTable(String tableName, Schema schema) {
    if (tableName == null) {
        tableName = schema.getName();
    }
    LOG.debug("Converting to Ddl tableName {}", tableName);
    Table.Builder table = Table.builder(dialect);
    table.name(tableName);
    for (Schema.Field f : schema.getFields()) {
        Column.Builder column = table.column(f.name());
        String sqlType = f.getProp("sqlType");
        String expression = f.getProp("generationExpression");
        if (expression != null) {
            // This is a generated column.
            if (Strings.isNullOrEmpty(sqlType)) {
                throw new IllegalArgumentException("Property sqlType is missing for generated column " + f.name());
            }
            String notNull = f.getProp("notNull");
            if (notNull == null) {
                throw new IllegalArgumentException("Property notNull is missing for generated column " + f.name());
            }
            column.parseType(sqlType).notNull(Boolean.parseBoolean(notNull)).generatedAs(expression);
            String stored = f.getProp("stored");
            if (stored == null) {
                throw new IllegalArgumentException("Property stored is missing for generated column " + f.name());
            }
            if (Boolean.parseBoolean(stored)) {
                column.stored();
            }
        } else {
            boolean nullable = false;
            Schema avroType = f.schema();
            if (avroType.getType() == Schema.Type.UNION) {
                Schema unpacked = unpackNullable(avroType);
                nullable = unpacked != null;
                if (nullable) {
                    avroType = unpacked;
                }
            }
            if (Strings.isNullOrEmpty(sqlType)) {
                Type spannerType = inferType(avroType, true);
                sqlType = toString(spannerType, true);
            }
            String defaultExpression = f.getProp("defaultExpression");
            column.parseType(sqlType).notNull(!nullable).defaultExpression(defaultExpression);
        }
        ImmutableList.Builder<String> columnOptions = ImmutableList.builder();
        for (int i = 0; ; i++) {
            String spannerOption = f.getProp("spannerOption_" + i);
            if (spannerOption == null) {
                break;
            }
            columnOptions.add(spannerOption);
        }
        column.columnOptions(columnOptions.build());
        column.endColumn();
    }
    for (int i = 0; ; i++) {
        String spannerPrimaryKey = schema.getProp("spannerPrimaryKey_" + i);
        if (spannerPrimaryKey == null) {
            break;
        }
        if (spannerPrimaryKey.endsWith(" ASC")) {
            String name = spannerPrimaryKey.substring(0, spannerPrimaryKey.length() - 4);
            table.primaryKey().asc(unescape(name, dialect)).end();
        } else if (spannerPrimaryKey.endsWith(" DESC")) {
            String name = spannerPrimaryKey.substring(0, spannerPrimaryKey.length() - 5);
            table.primaryKey().desc(unescape(name, dialect)).end();
        } else {
            throw new IllegalArgumentException("Cannot parse spannerPrimaryKey " + spannerPrimaryKey);
        }
    }
    table.indexes(getNumberedPropsWithPrefix(schema, "spannerIndex_"));
    table.foreignKeys(getNumberedPropsWithPrefix(schema, "spannerForeignKey_"));
    table.checkConstraints(getNumberedPropsWithPrefix(schema, "spannerCheckConstraint_"));
    // Table parent options.
    String spannerParent = schema.getProp("spannerParent");
    if (!Strings.isNullOrEmpty(spannerParent)) {
        table.interleaveInParent(spannerParent);
        // Process the on delete action.
        String onDeleteAction = schema.getProp("spannerOnDeleteAction");
        if (onDeleteAction == null) {
            // Preserve behavior for old versions of exporter that did not provide the
            // spannerOnDeleteAction property.
            onDeleteAction = "no action";
        }
        if (onDeleteAction.equals("cascade")) {
            table.onDeleteCascade();
        } else if (!onDeleteAction.equals("no action")) {
            // This is an unknown on delete action.
            throw new IllegalArgumentException("Unsupported ON DELETE action " + onDeleteAction);
        }
    }
    return table.build();
}
Also used : Table(com.google.cloud.teleport.spanner.ddl.Table) ImmutableList(com.google.common.collect.ImmutableList) Schema(org.apache.avro.Schema) LogicalType(org.apache.avro.LogicalType) Type(com.google.cloud.teleport.spanner.common.Type) Column(com.google.cloud.teleport.spanner.ddl.Column)

Example 3 with Column

use of com.google.cloud.teleport.spanner.ddl.Column in project DataflowTemplates by GoogleCloudPlatform.

the class DdlToAvroSchemaConverter method convert.

public Collection<Schema> convert(Ddl ddl) {
    Collection<Schema> schemas = new ArrayList<>();
    for (Table table : ddl.allTables()) {
        SchemaBuilder.RecordBuilder<Schema> recordBuilder = SchemaBuilder.record(table.name()).namespace(this.namespace);
        recordBuilder.prop("googleFormatVersion", version);
        recordBuilder.prop("googleStorage", "CloudSpanner");
        if (table.interleaveInParent() != null) {
            recordBuilder.prop("spannerParent", table.interleaveInParent());
            recordBuilder.prop("spannerOnDeleteAction", table.onDeleteCascade() ? "cascade" : "no action");
        }
        if (table.dialect() == Dialect.GOOGLE_STANDARD_SQL) {
            if (table.primaryKeys() != null) {
                String encodedPk = table.primaryKeys().stream().map(IndexColumn::prettyPrint).collect(Collectors.joining(","));
                recordBuilder.prop("spannerPrimaryKey", encodedPk);
            }
            for (int i = 0; i < table.primaryKeys().size(); i++) {
                recordBuilder.prop("spannerPrimaryKey_" + i, table.primaryKeys().get(i).prettyPrint());
            }
        } else if (table.dialect() == Dialect.POSTGRESQL) {
            if (table.primaryKeys() != null) {
                String encodedPk = table.primaryKeys().stream().map(c -> "\"" + c.name() + "\"").collect(Collectors.joining(", "));
                recordBuilder.prop("spannerPrimaryKey", encodedPk);
            }
            for (int i = 0; i < table.primaryKeys().size(); i++) {
                IndexColumn pk = table.primaryKeys().get(i);
                recordBuilder.prop("spannerPrimaryKey_" + i, "\"" + pk.name() + "\" ASC");
            }
        }
        for (int i = 0; i < table.indexes().size(); i++) {
            recordBuilder.prop("spannerIndex_" + i, table.indexes().get(i));
        }
        for (int i = 0; i < table.foreignKeys().size(); i++) {
            recordBuilder.prop("spannerForeignKey_" + i, table.foreignKeys().get(i));
        }
        for (int i = 0; i < table.checkConstraints().size(); i++) {
            recordBuilder.prop("spannerCheckConstraint_" + i, table.checkConstraints().get(i));
        }
        SchemaBuilder.FieldAssembler<Schema> fieldsAssembler = recordBuilder.fields();
        for (Column cm : table.columns()) {
            SchemaBuilder.FieldBuilder<Schema> fieldBuilder = fieldsAssembler.name(cm.name());
            fieldBuilder.prop("sqlType", cm.typeString());
            for (int i = 0; i < cm.columnOptions().size(); i++) {
                fieldBuilder.prop("spannerOption_" + i, cm.columnOptions().get(i));
            }
            if (cm.isGenerated()) {
                fieldBuilder.prop("notNull", Boolean.toString(cm.notNull()));
                fieldBuilder.prop("generationExpression", cm.generationExpression());
                fieldBuilder.prop("stored", Boolean.toString(cm.isStored()));
                // Make the type null to allow us not export the generated column values,
                // which are semantically logical entities.
                fieldBuilder.type(SchemaBuilder.builder().nullType()).withDefault(null);
            } else {
                if (cm.defaultExpression() != null) {
                    fieldBuilder.prop("defaultExpression", cm.defaultExpression());
                }
                Schema avroType = avroType(cm.type());
                if (!cm.notNull()) {
                    avroType = wrapAsNullable(avroType);
                }
                fieldBuilder.type(avroType).noDefault();
            }
        }
        Schema schema = fieldsAssembler.endRecord();
        schemas.add(schema);
    }
    for (View view : ddl.views()) {
        SchemaBuilder.RecordBuilder<Schema> recordBuilder = SchemaBuilder.record(view.name()).namespace(this.namespace);
        recordBuilder.prop("googleFormatVersion", version);
        recordBuilder.prop("googleStorage", "CloudSpanner");
        recordBuilder.prop("spannerViewQuery", view.query());
        if (view.security() != null) {
            recordBuilder.prop("spannerViewSecurity", view.security().toString());
        }
        schemas.add(recordBuilder.fields().endRecord());
    }
    for (ChangeStream changeStream : ddl.changeStreams()) {
        SchemaBuilder.RecordBuilder<Schema> recordBuilder = SchemaBuilder.record(changeStream.name()).namespace(this.namespace);
        recordBuilder.prop("googleFormatVersion", version);
        recordBuilder.prop("googleStorage", "CloudSpanner");
        recordBuilder.prop(AvroUtil.CHANGE_STREAM_FOR_CLAUSE, changeStream.forClause() == null ? "" : changeStream.forClause());
        if (changeStream.options() != null) {
            for (int i = 0; i < changeStream.options().size(); i++) {
                recordBuilder.prop("spannerOption_" + i, changeStream.options().get(i));
            }
        }
        schemas.add(recordBuilder.fields().endRecord());
    }
    return schemas;
}
Also used : Table(com.google.cloud.teleport.spanner.ddl.Table) Schema(org.apache.avro.Schema) ArrayList(java.util.ArrayList) View(com.google.cloud.teleport.spanner.ddl.View) IndexColumn(com.google.cloud.teleport.spanner.ddl.IndexColumn) IndexColumn(com.google.cloud.teleport.spanner.ddl.IndexColumn) Column(com.google.cloud.teleport.spanner.ddl.Column) SchemaBuilder(org.apache.avro.SchemaBuilder) ChangeStream(com.google.cloud.teleport.spanner.ddl.ChangeStream)

Aggregations

Column (com.google.cloud.teleport.spanner.ddl.Column)3 Schema (org.apache.avro.Schema)3 Table (com.google.cloud.teleport.spanner.ddl.Table)2 LogicalType (org.apache.avro.LogicalType)2 Mutation (com.google.cloud.spanner.Mutation)1 Type (com.google.cloud.teleport.spanner.common.Type)1 ChangeStream (com.google.cloud.teleport.spanner.ddl.ChangeStream)1 IndexColumn (com.google.cloud.teleport.spanner.ddl.IndexColumn)1 View (com.google.cloud.teleport.spanner.ddl.View)1 ImmutableList (com.google.common.collect.ImmutableList)1 ArrayList (java.util.ArrayList)1 SchemaBuilder (org.apache.avro.SchemaBuilder)1