Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion flow/activities/flowable_core.go
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,9 @@ func syncCore[TPull connectors.CDCPullConnectorCore, TSync connectors.CDCSyncCon
defer dstClose(ctx)

syncState.Store(shared.Ptr("updating schema"))
if err := dstConn.ReplayTableSchemaDeltas(ctx, config.Env, flowName, options.TableMappings, recordBatchSync.SchemaDeltas); err != nil {
if err := dstConn.ReplayTableSchemaDeltas(
ctx, config.Env, flowName, options.TableMappings, recordBatchSync.SchemaDeltas, config.Version,
); err != nil {
return nil, fmt.Errorf("failed to sync schema: %w", err)
}

Expand Down
1 change: 1 addition & 0 deletions flow/connectors/bigquery/bigquery.go
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ func (c *BigQueryConnector) ReplayTableSchemaDeltas(
flowJobName string,
_ []*protos.TableMapping,
schemaDeltas []*protos.TableSchemaDelta,
_ uint32,
) error {
for _, schemaDelta := range schemaDeltas {
if schemaDelta == nil || len(schemaDelta.AddedColumns) == 0 {
Expand Down
2 changes: 1 addition & 1 deletion flow/connectors/bigquery/qrep.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func (c *BigQueryConnector) replayTableSchemaDeltasQRep(
}

if err := c.ReplayTableSchemaDeltas(
ctx, config.Env, config.FlowJobName, nil, []*protos.TableSchemaDelta{tableSchemaDelta},
ctx, config.Env, config.FlowJobName, nil, []*protos.TableSchemaDelta{tableSchemaDelta}, config.Version,
); err != nil {
return nil, fmt.Errorf("failed to add columns to destination table: %w", err)
}
Expand Down
4 changes: 3 additions & 1 deletion flow/connectors/bigquery/qrep_avro_sync.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ func (s *QRepAvroSyncMethod) SyncRecords(
slog.String(string(shared.FlowNameKey), req.FlowJobName),
slog.String("dstTableName", rawTableName))

if err := s.connector.ReplayTableSchemaDeltas(ctx, req.Env, req.FlowJobName, req.TableMappings, req.Records.SchemaDeltas); err != nil {
if err := s.connector.ReplayTableSchemaDeltas(
ctx, req.Env, req.FlowJobName, req.TableMappings, req.Records.SchemaDeltas, req.Version,
); err != nil {
return nil, fmt.Errorf("failed to sync schema changes: %w", err)
}

Expand Down
5 changes: 3 additions & 2 deletions flow/connectors/clickhouse/cdc.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func (c *ClickHouseConnector) syncRecordsViaAvro(
}
warnings := numericTruncator.Warnings()

if err := c.ReplayTableSchemaDeltas(ctx, req.Env, req.FlowJobName, req.TableMappings, req.Records.SchemaDeltas); err != nil {
if err := c.ReplayTableSchemaDeltas(ctx, req.Env, req.FlowJobName, req.TableMappings, req.Records.SchemaDeltas, req.Version); err != nil {
return nil, fmt.Errorf("failed to sync schema changes: %w", err)
}

Expand Down Expand Up @@ -165,6 +165,7 @@ func (c *ClickHouseConnector) ReplayTableSchemaDeltas(
flowJobName string,
tableMappings []*protos.TableMapping,
schemaDeltas []*protos.TableSchemaDelta,
internalVersion uint32,
) error {
if len(schemaDeltas) == 0 {
return nil
Expand All @@ -188,7 +189,7 @@ func (c *ClickHouseConnector) ReplayTableSchemaDeltas(
for _, addedColumn := range schemaDelta.AddedColumns {
qvKind := types.QValueKind(addedColumn.Type)
clickHouseColType, err := qvalue.ToDWHColumnType(
ctx, qvKind, env, protos.DBType_CLICKHOUSE, c.chVersion, addedColumn, schemaDelta.NullableEnabled,
ctx, qvKind, env, protos.DBType_CLICKHOUSE, c.chVersion, addedColumn, schemaDelta.NullableEnabled, internalVersion,
)
if err != nil {
return fmt.Errorf("failed to convert column type %s to ClickHouse type: %w", addedColumn.Type, err)
Expand Down
2 changes: 2 additions & 0 deletions flow/connectors/clickhouse/clickhouse.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,8 @@ func GetTableSchemaForTable(tm *protos.TableMapping, columns []driver.ColumnType
qkind = types.QValueKindUUID
case "DateTime64(6)", "Nullable(DateTime64(6))", "DateTime64(9)", "Nullable(DateTime64(9))":
qkind = types.QValueKindTimestamp
case "Time64(3)", "Nullable(Time64(3))":
qkind = types.QValueKindTime
case "Date32", "Nullable(Date32)":
qkind = types.QValueKindDate
case "Float32", "Nullable(Float32)":
Expand Down
5 changes: 4 additions & 1 deletion flow/connectors/clickhouse/normalize.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func (c *ClickHouseConnector) SetupNormalizedTable(
destinationTableIdentifier,
sourceTableSchema,
c.chVersion,
config.Version,
)
if err != nil {
return false, fmt.Errorf("error while generating create table sql for destination ClickHouse table: %w", err)
Expand All @@ -85,6 +86,7 @@ func (c *ClickHouseConnector) generateCreateTableSQLForNormalizedTable(
tableIdentifier string,
tableSchema *protos.TableSchema,
chVersion *chproto.Version,
internalVersion uint32,
) ([]string, error) {
var engine string
tmEngine := protos.TableEngine_CH_ENGINE_REPLACING_MERGE_TREE
Expand Down Expand Up @@ -203,7 +205,8 @@ func (c *ClickHouseConnector) generateCreateTableSQLForNormalizedTable(
if clickHouseType == "" {
var err error
clickHouseType, err = qvalue.ToDWHColumnType(
ctx, colType, config.Env, protos.DBType_CLICKHOUSE, chVersion, column, tableSchema.NullableEnabled || columnNullableEnabled,
ctx, colType, config.Env, protos.DBType_CLICKHOUSE, chVersion, column,
tableSchema.NullableEnabled || columnNullableEnabled, internalVersion,
)
if err != nil {
return nil, fmt.Errorf("error while converting column type to ClickHouse type: %w", err)
Expand Down
17 changes: 16 additions & 1 deletion flow/connectors/clickhouse/normalize_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,14 +123,29 @@ func (t *NormalizeQueryGenerator) BuildQuery(ctx context.Context) (string, error
if clickHouseType == "" {
var err error
clickHouseType, err = qvalue.ToDWHColumnType(
ctx, colType, t.env, protos.DBType_CLICKHOUSE, t.chVersion, column, schema.NullableEnabled || columnNullableEnabled,
ctx, colType, t.env, protos.DBType_CLICKHOUSE, t.chVersion, column, schema.NullableEnabled || columnNullableEnabled, t.version,
)
if err != nil {
return "", fmt.Errorf("error while converting column type to clickhouse type: %w", err)
}
}

switch clickHouseType {
case "Time64(3)", "Nullable(Time64(3))":
// Time64 is a time-of-day type, parse from JSON string
// toTime64 converts string to Time64(3), returns NULL if string is NULL or invalid
fmt.Fprintf(&projection,
"toTime64(JSONExtractString(_peerdb_data, %s),3) AS %s,",
peerdb_clickhouse.QuoteLiteral(colName),
peerdb_clickhouse.QuoteIdentifier(dstColName),
)
if t.enablePrimaryUpdate {
fmt.Fprintf(&projectionUpdate,
"toTime64(JSONExtractString(_peerdb_match_data, %s),3) AS %s,",
peerdb_clickhouse.QuoteLiteral(colName),
peerdb_clickhouse.QuoteIdentifier(dstColName),
)
}
case "Date32", "Nullable(Date32)":
fmt.Fprintf(&projection,
"toDate32(parseDateTime64BestEffortOrNull(JSONExtractString(_peerdb_data, %s),6,'UTC')) AS %s,",
Expand Down
2 changes: 1 addition & 1 deletion flow/connectors/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ type CDCSyncConnectorCore interface {
// This could involve adding multiple columns.
// Connectors which are non-normalizing should implement this as a nop.
ReplayTableSchemaDeltas(ctx context.Context, env map[string]string, flowJobName string,
tableMappings []*protos.TableMapping, schemaDeltas []*protos.TableSchemaDelta) error
tableMappings []*protos.TableMapping, schemaDeltas []*protos.TableSchemaDelta, internalVersion uint32) error
}

type CDCSyncConnector interface {
Expand Down
2 changes: 1 addition & 1 deletion flow/connectors/elasticsearch/elasticsearch.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ func (esc *ElasticsearchConnector) CreateRawTable(ctx context.Context,

// we handle schema changes by not handling them since no mapping is being enforced right now
func (esc *ElasticsearchConnector) ReplayTableSchemaDeltas(ctx context.Context, env map[string]string,
flowJobName string, _ []*protos.TableMapping, schemaDeltas []*protos.TableSchemaDelta,
flowJobName string, _ []*protos.TableMapping, schemaDeltas []*protos.TableSchemaDelta, _ uint32,
) error {
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion flow/connectors/eventhub/eventhub.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ func (c *EventHubConnector) CreateRawTable(ctx context.Context, req *protos.Crea
}

func (c *EventHubConnector) ReplayTableSchemaDeltas(_ context.Context, _ map[string]string,
flowJobName string, _ []*protos.TableMapping, schemaDeltas []*protos.TableSchemaDelta,
flowJobName string, _ []*protos.TableMapping, schemaDeltas []*protos.TableSchemaDelta, _ uint32,
) error {
return nil
}
2 changes: 1 addition & 1 deletion flow/connectors/kafka/kafka.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func (c *KafkaConnector) CreateRawTable(ctx context.Context, req *protos.CreateR
}

func (c *KafkaConnector) ReplayTableSchemaDeltas(_ context.Context, _ map[string]string,
flowJobName string, _ []*protos.TableMapping, schemaDeltas []*protos.TableSchemaDelta,
flowJobName string, _ []*protos.TableMapping, schemaDeltas []*protos.TableSchemaDelta, _ uint32,
) error {
return nil
}
Expand Down
7 changes: 4 additions & 3 deletions flow/connectors/mysql/cdc.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (c *MySqlConnector) GetTableSchema(
) (map[string]*protos.TableSchema, error) {
res := make(map[string]*protos.TableSchema, len(tableMappings))
for _, tm := range tableMappings {
tableSchema, err := c.getTableSchemaForTable(ctx, env, tm, system)
tableSchema, err := c.getTableSchemaForTable(ctx, env, tm, system, version)
if err != nil {
c.logger.Info("error fetching schema", slog.String("table", tm.SourceTableIdentifier), slog.Any("error", err))
return nil, err
Expand All @@ -57,6 +57,7 @@ func (c *MySqlConnector) getTableSchemaForTable(
env map[string]string,
tm *protos.TableMapping,
system protos.TypeSystem,
version uint32,
) (*protos.TableSchema, error) {
schemaTable, err := utils.ParseSchemaTable(tm.SourceTableIdentifier)
if err != nil {
Expand Down Expand Up @@ -106,7 +107,7 @@ func (c *MySqlConnector) getTableSchemaForTable(
if err != nil {
return nil, err
}
qkind, err := QkindFromMysqlColumnType(dataType)
qkind, err := QkindFromMysqlColumnType(dataType, version)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -699,7 +700,7 @@ func (c *MySqlConnector) processAlterTableQuery(ctx context.Context, catalogPool
slog.String("tableName", sourceTableName))
continue
}
qkind, err := QkindFromMysqlColumnType(col.Tp.InfoSchemaStr())
qkind, err := QkindFromMysqlColumnType(col.Tp.InfoSchemaStr(), req.InternalVersion)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion flow/connectors/mysql/qrep.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ func (c *MySqlConnector) PullQRepRecords(
stream *model.QRecordStream,
) (int64, int64, error) {
tableSchema, err := c.getTableSchemaForTable(ctx, config.Env,
&protos.TableMapping{SourceTableIdentifier: config.WatermarkTable}, protos.TypeSystem_Q)
&protos.TableMapping{SourceTableIdentifier: config.WatermarkTable}, protos.TypeSystem_Q, config.Version)
if err != nil {
return 0, 0, fmt.Errorf("failed to get schema for watermark table %s: %w", config.WatermarkTable, err)
}
Expand Down
2 changes: 1 addition & 1 deletion flow/connectors/mysql/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ func (c *MySqlConnector) GetColumns(ctx context.Context, version uint32, schema
if err != nil {
return nil, err
}
qkind, err := QkindFromMysqlColumnType(columnType)
qkind, err := QkindFromMysqlColumnType(columnType, version)
if err != nil {
return nil, err
}
Expand Down
11 changes: 9 additions & 2 deletions flow/connectors/mysql/type_conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ import (
"fmt"
"strings"

"github.com/PeerDB-io/peerdb/flow/shared"
"github.com/PeerDB-io/peerdb/flow/shared/types"
)

func QkindFromMysqlColumnType(ct string) (types.QValueKind, error) {
func QkindFromMysqlColumnType(ct string, version uint32) (types.QValueKind, error) {
// https://mariadb.com/docs/server/reference/data-types/date-and-time-data-types/timestamp#tab-current-1
ct, _ = strings.CutSuffix(ct, " /* mariadb-5.3 */")
ct, _ = strings.CutSuffix(ct, " zerofill")
Expand All @@ -24,7 +25,13 @@ func QkindFromMysqlColumnType(ct string) (types.QValueKind, error) {
return types.QValueKindBytes, nil
case "date":
return types.QValueKindDate, nil
case "datetime", "timestamp", "time":
case "datetime", "timestamp":
return types.QValueKindTimestamp, nil
case "time":
// For new versions, map TIME to QValueKindTime instead of QValueKindTimestamp
if version >= shared.InternalVersion_ClickHouseTime64 {
return types.QValueKindTime, nil
}
return types.QValueKindTimestamp, nil
case "decimal", "numeric":
return types.QValueKindNumeric, nil
Expand Down
3 changes: 2 additions & 1 deletion flow/connectors/postgres/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -650,7 +650,7 @@ func syncRecordsCore[Items model.Items](
return nil, err
}

if err := c.ReplayTableSchemaDeltas(ctx, req.Env, req.FlowJobName, req.TableMappings, req.Records.SchemaDeltas); err != nil {
if err := c.ReplayTableSchemaDeltas(ctx, req.Env, req.FlowJobName, req.TableMappings, req.Records.SchemaDeltas, req.Version); err != nil {
return nil, fmt.Errorf("failed to sync schema changes: %w", err)
}

Expand Down Expand Up @@ -1120,6 +1120,7 @@ func (c *PostgresConnector) ReplayTableSchemaDeltas(
flowJobName string,
_ []*protos.TableMapping,
schemaDeltas []*protos.TableSchemaDelta,
_ uint32,
) error {
if len(schemaDeltas) == 0 {
return nil
Expand Down
8 changes: 4 additions & 4 deletions flow/connectors/postgres/postgres_schema_delta_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func (s PostgresSchemaDeltaTestSuite) TestSimpleAddColumn() {
Nullable: true,
},
},
}}))
}}, shared.InternalVersion_Latest))

output, err := s.connector.GetTableSchema(s.t.Context(), nil, shared.InternalVersion_Latest, protos.TypeSystem_Q,
[]*protos.TableMapping{{SourceTableIdentifier: tableName}})
Expand Down Expand Up @@ -115,7 +115,7 @@ func (s PostgresSchemaDeltaTestSuite) TestAddAllColumnTypes() {
SrcTableName: tableName,
DstTableName: tableName,
AddedColumns: addedColumns,
}}))
}}, shared.InternalVersion_Latest))

output, err := s.connector.GetTableSchema(s.t.Context(), nil, shared.InternalVersion_Latest, protos.TypeSystem_Q,
[]*protos.TableMapping{{SourceTableIdentifier: tableName}})
Expand Down Expand Up @@ -146,7 +146,7 @@ func (s PostgresSchemaDeltaTestSuite) TestAddTrickyColumnNames() {
SrcTableName: tableName,
DstTableName: tableName,
AddedColumns: addedColumns,
}}))
}}, shared.InternalVersion_Latest))

output, err := s.connector.GetTableSchema(s.t.Context(), nil, shared.InternalVersion_Latest, protos.TypeSystem_Q,
[]*protos.TableMapping{{SourceTableIdentifier: tableName}})
Expand Down Expand Up @@ -177,7 +177,7 @@ func (s PostgresSchemaDeltaTestSuite) TestAddDropWhitespaceColumnNames() {
SrcTableName: tableName,
DstTableName: tableName,
AddedColumns: addedColumns,
}}))
}}, shared.InternalVersion_Latest))

output, err := s.connector.GetTableSchema(s.t.Context(), nil, shared.InternalVersion_Latest, protos.TypeSystem_Q,
[]*protos.TableMapping{{SourceTableIdentifier: tableName}})
Expand Down
2 changes: 1 addition & 1 deletion flow/connectors/pubsub/pubsub.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func (c *PubSubConnector) CreateRawTable(ctx context.Context, req *protos.Create
}

func (c *PubSubConnector) ReplayTableSchemaDeltas(_ context.Context, _ map[string]string,
flowJobName string, _ []*protos.TableMapping, schemaDeltas []*protos.TableSchemaDelta,
flowJobName string, _ []*protos.TableMapping, schemaDeltas []*protos.TableSchemaDelta, _ uint32,
) error {
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion flow/connectors/s3/s3.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ func (c *S3Connector) SyncRecords(ctx context.Context, req *model.SyncRecordsReq
}

func (c *S3Connector) ReplayTableSchemaDeltas(_ context.Context, _ map[string]string,
flowJobName string, _ []*protos.TableMapping, schemaDeltas []*protos.TableSchemaDelta,
flowJobName string, _ []*protos.TableMapping, schemaDeltas []*protos.TableSchemaDelta, _ uint32,
) error {
return nil
}
2 changes: 1 addition & 1 deletion flow/connectors/snowflake/merge_stmt_generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func (m *mergeStmtGenerator) generateMergeStmt(ctx context.Context, env map[stri
for _, column := range columns {
genericColumnType := column.Type
qvKind := types.QValueKind(genericColumnType)
sfType, err := qvalue.ToDWHColumnType(ctx, qvKind, env, protos.DBType_SNOWFLAKE, nil, column, normalizedTableSchema.NullableEnabled)
sfType, err := qvalue.ToDWHColumnType(ctx, qvKind, env, protos.DBType_SNOWFLAKE, nil, column, normalizedTableSchema.NullableEnabled, 0)
if err != nil {
return "", fmt.Errorf("failed to convert column type %s to snowflake type: %w", genericColumnType, err)
}
Expand Down
7 changes: 4 additions & 3 deletions flow/connectors/snowflake/snowflake.go
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ func (c *SnowflakeConnector) ReplayTableSchemaDeltas(
flowJobName string,
_ []*protos.TableMapping,
schemaDeltas []*protos.TableSchemaDelta,
_ uint32,
) error {
if len(schemaDeltas) == 0 {
return nil
Expand All @@ -365,7 +366,7 @@ func (c *SnowflakeConnector) ReplayTableSchemaDeltas(
for _, addedColumn := range schemaDelta.AddedColumns {
qvKind := types.QValueKind(addedColumn.Type)
sfColtype, err := qvalue.ToDWHColumnType(
ctx, qvKind, env, protos.DBType_SNOWFLAKE, nil, addedColumn, schemaDelta.NullableEnabled,
ctx, qvKind, env, protos.DBType_SNOWFLAKE, nil, addedColumn, schemaDelta.NullableEnabled, 0,
)
if err != nil {
return fmt.Errorf("failed to convert column type %s to snowflake type: %w",
Expand Down Expand Up @@ -450,7 +451,7 @@ func (c *SnowflakeConnector) syncRecordsViaAvro(
return nil, err
}

if err := c.ReplayTableSchemaDeltas(ctx, req.Env, req.FlowJobName, req.TableMappings, req.Records.SchemaDeltas); err != nil {
if err := c.ReplayTableSchemaDeltas(ctx, req.Env, req.FlowJobName, req.TableMappings, req.Records.SchemaDeltas, req.Version); err != nil {
return nil, fmt.Errorf("failed to sync schema changes: %w", err)
}

Expand Down Expand Up @@ -663,7 +664,7 @@ func generateCreateTableSQLForNormalizedTable(
normalizedColName := SnowflakeIdentifierNormalize(column.Name)
qvKind := types.QValueKind(genericColumnType)
sfColType, err := qvalue.ToDWHColumnType(
ctx, qvKind, config.Env, protos.DBType_SNOWFLAKE, nil, column, tableSchema.NullableEnabled,
ctx, qvKind, config.Env, protos.DBType_SNOWFLAKE, nil, column, tableSchema.NullableEnabled, 0,
)
if err != nil {
slog.WarnContext(ctx, fmt.Sprintf("failed to convert column type %s to snowflake type", genericColumnType),
Expand Down
6 changes: 5 additions & 1 deletion flow/e2e/clickhouse.go
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,11 @@ func (s ClickHouseSuite) GetRows(table string, cols string) (*model.QRecordBatch
qrow = append(qrow, types.QValueUInt256{Val: *v})
}
case *time.Time:
qrow = append(qrow, types.QValueTimestamp{Val: *v})
if batch.Schema.Fields[idx].Type == types.QValueKindTime {
qrow = append(qrow, types.QValueTime{Val: v.Sub(v.Truncate(time.Hour * 24))})
} else {
qrow = append(qrow, types.QValueTimestamp{Val: *v})
}
case *[]time.Time:
qrow = append(qrow, types.QValueArrayTimestamp{Val: *v})
case **decimal.Decimal:
Expand Down
Loading
Loading