From 59dfff74a98a4d6282997f9c5765eb2e62ba6ecd Mon Sep 17 00:00:00 2001 From: Joy Gao <17896160+jgao54@users.noreply.github.com> Date: Wed, 4 Jun 2025 13:17:05 -1000 Subject: [PATCH] use direct src to dst type mapping for conversion --- flow/connectors/mysql/qvalue_convert.go | 3 +- flow/connectors/mysql/schema.go | 9 +- flow/connectors/postgres/client.go | 3 +- .../postgres/qrep_query_executor.go | 3 +- flow/connectors/postgres/qvalue_convert.go | 31 +++- .../postgres/qvalue_convert_test.go | 174 ++++++++++++++++++ flow/connectors/postgres/schema.go | 18 +- flow/datatypes/numeric.go | 20 +- flow/model/qvalue/kind.go | 3 +- flow/shared/clickhouse/type_conversion.go | 101 ++++++++++ flow/shared/mysql/types.go | 1 + flow/shared/postgres.go | 15 ++ protos/route.proto | 11 ++ 13 files changed, 355 insertions(+), 37 deletions(-) create mode 100644 flow/connectors/postgres/qvalue_convert_test.go create mode 100644 flow/shared/clickhouse/type_conversion.go create mode 100644 flow/shared/mysql/types.go diff --git a/flow/connectors/mysql/qvalue_convert.go b/flow/connectors/mysql/qvalue_convert.go index 71b67e5e2..92a4307d5 100644 --- a/flow/connectors/mysql/qvalue_convert.go +++ b/flow/connectors/mysql/qvalue_convert.go @@ -15,7 +15,6 @@ import ( "github.com/shopspring/decimal" geom "github.com/twpayne/go-geos" - "github.com/PeerDB-io/peerdb/flow/datatypes" "github.com/PeerDB-io/peerdb/flow/generated/protos" "github.com/PeerDB-io/peerdb/flow/model/qvalue" "github.com/PeerDB-io/peerdb/flow/shared" @@ -177,7 +176,7 @@ func QRecordSchemaFromMysqlFields(tableSchema *protos.TableSchema, fields []*mys if col, ok := tableColumns[name]; ok { qkind = qvalue.QValueKind(col.Type) if qkind == qvalue.QValueKindNumeric { - precision, scale = datatypes.ParseNumericTypmod(col.TypeModifier) + precision, scale = shared.ParseNumericTypmod(col.TypeModifier) } } else { var err error diff --git a/flow/connectors/mysql/schema.go b/flow/connectors/mysql/schema.go index 56a4d0aab..346ba5059 100644 --- a/flow/connectors/mysql/schema.go +++ b/flow/connectors/mysql/schema.go @@ -100,10 +100,11 @@ func (c *MySqlConnector) GetColumns(ctx context.Context, schema string, table st return nil, err } columns = append(columns, &protos.ColumnsItem{ - Name: columnName, - Type: columnType, - IsKey: columnKey == "PRI", - Qkind: string(qkind), + Name: columnName, + Type: columnType, + IsKey: columnKey == "PRI", + Qkind: string(qkind), + TypeInfo: &protos.ColumnsItem_NullTypeInfo{}, }) } return &protos.TableColumnsResponse{Columns: columns}, nil diff --git a/flow/connectors/postgres/client.go b/flow/connectors/postgres/client.go index 7632db8dd..2e5e8be96 100644 --- a/flow/connectors/postgres/client.go +++ b/flow/connectors/postgres/client.go @@ -16,7 +16,6 @@ import ( "github.com/lib/pq/oid" "github.com/PeerDB-io/peerdb/flow/connectors/utils" - numeric "github.com/PeerDB-io/peerdb/flow/datatypes" "github.com/PeerDB-io/peerdb/flow/generated/protos" "github.com/PeerDB-io/peerdb/flow/model" "github.com/PeerDB-io/peerdb/flow/shared" @@ -471,7 +470,7 @@ func generateCreateTableSQLForNormalizedTable( pgColumnType = qValueKindToPostgresType(pgColumnType) } if column.Type == "numeric" && column.TypeModifier != -1 { - precision, scale := numeric.ParseNumericTypmod(column.TypeModifier) + precision, scale := shared.ParseNumericTypmod(column.TypeModifier) pgColumnType = fmt.Sprintf("numeric(%d,%d)", precision, scale) } var notNull string diff --git a/flow/connectors/postgres/qrep_query_executor.go b/flow/connectors/postgres/qrep_query_executor.go index 97244c4e5..f76f50ef8 100644 --- a/flow/connectors/postgres/qrep_query_executor.go +++ b/flow/connectors/postgres/qrep_query_executor.go @@ -10,7 +10,6 @@ import ( "github.com/jackc/pgx/v5/pgtype" "go.temporal.io/sdk/log" - "github.com/PeerDB-io/peerdb/flow/datatypes" "github.com/PeerDB-io/peerdb/flow/model" "github.com/PeerDB-io/peerdb/flow/model/qvalue" "github.com/PeerDB-io/peerdb/flow/shared" @@ -76,7 +75,7 @@ func (qe *QRepQueryExecutor) fieldDescriptionsToSchema(fds []pgconn.FieldDescrip ctype := qe.postgresOIDToQValueKind(fd.DataTypeOID, qe.customTypeMapping) // there isn't a way to know if a column is nullable or not if ctype == qvalue.QValueKindNumeric { - precision, scale := datatypes.ParseNumericTypmod(fd.TypeModifier) + precision, scale := shared.ParseNumericTypmod(fd.TypeModifier) qfields[i] = qvalue.QField{ Name: fd.Name, Type: ctype, diff --git a/flow/connectors/postgres/qvalue_convert.go b/flow/connectors/postgres/qvalue_convert.go index 1b0a467b9..d299c134d 100644 --- a/flow/connectors/postgres/qvalue_convert.go +++ b/flow/connectors/postgres/qvalue_convert.go @@ -14,6 +14,7 @@ import ( "github.com/jackc/pgx/v5/pgtype" "github.com/lib/pq/oid" "github.com/shopspring/decimal" + "go.temporal.io/sdk/log" datatypes "github.com/PeerDB-io/peerdb/flow/datatypes" "github.com/PeerDB-io/peerdb/flow/model/qvalue" @@ -21,7 +22,16 @@ import ( ) func (c *PostgresConnector) postgresOIDToName(recvOID uint32, customTypeMapping map[uint32]shared.CustomDataType) (string, error) { - if ty, ok := c.typeMap.TypeForOID(recvOID); ok { + return postgresOidToName(recvOID, customTypeMapping, c.typeMap) +} + +// for unit testing +func postgresOidToName( + recvOID uint32, + customTypeMapping map[uint32]shared.CustomDataType, + typeMap *pgtype.Map, +) (string, error) { + if ty, ok := typeMap.TypeForOID(recvOID); ok { return ty.Name, nil } // workaround for some types not being defined by pgtype @@ -50,6 +60,17 @@ func (c *PostgresConnector) postgresOIDToName(recvOID uint32, customTypeMapping func (c *PostgresConnector) postgresOIDToQValueKind( recvOID uint32, customTypeMapping map[uint32]shared.CustomDataType, +) qvalue.QValueKind { + return postgresOIDToQValueKind(recvOID, customTypeMapping, c.typeMap, c.hushWarnOID, c.logger) +} + +// for unit testing +func postgresOIDToQValueKind( + recvOID uint32, + customTypeMapping map[uint32]shared.CustomDataType, + typeMap *pgtype.Map, + hushWarnOID map[uint32]struct{}, + logger log.Logger, ) qvalue.QValueKind { switch recvOID { case pgtype.BoolOID: @@ -125,15 +146,15 @@ func (c *PostgresConnector) postgresOIDToQValueKind( case pgtype.TstzrangeOID: return qvalue.QValueKindTSTZRange default: - if typeName, ok := c.typeMap.TypeForOID(recvOID); ok { + if typeName, ok := typeMap.TypeForOID(recvOID); ok { colType := qvalue.QValueKindString if typeData, ok := customTypeMapping[recvOID]; ok { colType = customTypeToQKind(typeData) } - if _, warned := c.hushWarnOID[recvOID]; !warned { - c.logger.Warn("unsupported field type", + if _, warned := hushWarnOID[recvOID]; !warned { + logger.Warn("unsupported field type", slog.Int64("oid", int64(recvOID)), slog.String("typeName", typeName.Name), slog.String("mapping", string(colType))) - c.hushWarnOID[recvOID] = struct{}{} + hushWarnOID[recvOID] = struct{}{} } return colType } else { diff --git a/flow/connectors/postgres/qvalue_convert_test.go b/flow/connectors/postgres/qvalue_convert_test.go new file mode 100644 index 000000000..05760b791 --- /dev/null +++ b/flow/connectors/postgres/qvalue_convert_test.go @@ -0,0 +1,174 @@ +package connpostgres + +import ( + "strconv" + "testing" + + "github.com/jackc/pgx/v5/pgtype" + "github.com/stretchr/testify/require" + + "github.com/PeerDB-io/peerdb/flow/generated/protos" + "github.com/PeerDB-io/peerdb/flow/internal" + "github.com/PeerDB-io/peerdb/flow/shared" + "github.com/PeerDB-io/peerdb/flow/shared/clickhouse" +) + +// TODO: turn this test into an integration test and use TestContainer to query +// an actual postgres database to fetch an exhaustive list of all pg types, to +// avoid explicitly listing all types. +var pgTypeInfo = []uint32{ + pgtype.BoolOID, + pgtype.ByteaOID, + pgtype.QCharOID, + pgtype.NameOID, + pgtype.Int8OID, + pgtype.Int2OID, + pgtype.Int4OID, + pgtype.TextOID, + pgtype.OIDOID, + pgtype.TIDOID, + pgtype.XIDOID, + pgtype.CIDOID, + pgtype.JSONOID, + pgtype.XMLOID, + pgtype.XMLArrayOID, + pgtype.JSONArrayOID, + pgtype.XID8ArrayOID, + pgtype.PointOID, + pgtype.LsegOID, + pgtype.PathOID, + pgtype.BoxOID, + pgtype.PolygonOID, + pgtype.LineOID, + pgtype.LineArrayOID, + pgtype.CIDROID, + pgtype.CIDRArrayOID, + pgtype.Float4OID, + pgtype.Float8OID, + pgtype.CircleOID, + pgtype.CircleArrayOID, + pgtype.UnknownOID, + pgtype.Macaddr8OID, + pgtype.MacaddrOID, + pgtype.InetOID, + pgtype.BoolArrayOID, + pgtype.QCharArrayOID, + pgtype.NameArrayOID, + pgtype.Int2ArrayOID, + pgtype.Int4ArrayOID, + pgtype.TextArrayOID, + pgtype.TIDArrayOID, + pgtype.ByteaArrayOID, + pgtype.XIDArrayOID, + pgtype.CIDArrayOID, + pgtype.BPCharArrayOID, + pgtype.VarcharArrayOID, + pgtype.Int8ArrayOID, + pgtype.PointArrayOID, + pgtype.LsegArrayOID, + pgtype.PathArrayOID, + pgtype.BoxArrayOID, + pgtype.Float4ArrayOID, + pgtype.Float8ArrayOID, + pgtype.PolygonArrayOID, + pgtype.OIDArrayOID, + pgtype.ACLItemOID, + pgtype.ACLItemArrayOID, + pgtype.MacaddrArrayOID, + pgtype.InetArrayOID, + pgtype.BPCharOID, + pgtype.VarcharOID, + pgtype.DateOID, + pgtype.TimeOID, + pgtype.TimestampOID, + pgtype.TimestampArrayOID, + pgtype.DateArrayOID, + pgtype.TimeArrayOID, + pgtype.TimestamptzOID, + pgtype.TimestamptzArrayOID, + pgtype.IntervalOID, + pgtype.IntervalArrayOID, + pgtype.NumericArrayOID, + pgtype.TimetzOID, + pgtype.BitOID, + pgtype.BitArrayOID, + pgtype.VarbitOID, + pgtype.VarbitArrayOID, + pgtype.NumericOID, + pgtype.RecordOID, + pgtype.RecordArrayOID, + pgtype.UUIDOID, + pgtype.UUIDArrayOID, + pgtype.JSONBOID, + pgtype.JSONBArrayOID, + pgtype.DaterangeOID, + pgtype.DaterangeArrayOID, + pgtype.Int4rangeOID, + pgtype.Int4rangeArrayOID, + pgtype.NumrangeOID, + pgtype.NumrangeArrayOID, + pgtype.TsrangeOID, + pgtype.TsrangeArrayOID, + pgtype.TstzrangeOID, + pgtype.TstzrangeArrayOID, + pgtype.Int8rangeOID, + pgtype.Int8rangeArrayOID, + pgtype.JSONPathOID, + pgtype.JSONPathArrayOID, + pgtype.Int4multirangeOID, + pgtype.NummultirangeOID, + pgtype.TsmultirangeOID, + pgtype.TstzmultirangeOID, + pgtype.DatemultirangeOID, + pgtype.Int8multirangeOID, + pgtype.XID8OID, + // pgtype.TimetzArrayOID, // not supported + // pgtype.Int4multirangeArrayOID, // not supported + // pgtype.NummultirangeArrayOID, // not supported + // pgtype.TsmultirangeArrayOID, // not supported + // pgtype.TstzmultirangeArrayOID, // not supported + // pgtype.DatemultirangeArrayOID, // not supported + // pgtype.Int8multirangeArrayOID, // not supported +} + +// TODO: add test cases for: +// 1. type modifiers != -1 +// 2. custom types +// 3. nullable=true +func TestPostgresOidToClickHouseType(t *testing.T) { + ctx := t.Context() + logger := internal.LoggerFromCtx(ctx) + newMap := pgtype.NewMap() + hushWarnOID := make(map[uint32]struct{}) + customTypeMap := map[uint32]shared.CustomDataType{} + env := map[string]string{ + "PEERDB_CLICKHOUSE_UNBOUNDED_NUMERIC_AS_STRING": "false", + } + nullable := false + nullableEnabled := false + for _, oid := range pgTypeInfo { + pgType, err := postgresOidToName(oid, customTypeMap, newMap) + require.NoError(t, err) + + fd := &protos.FieldDescription{ + Name: strconv.FormatUint(uint64(oid), 10), + TypeModifier: -1, + Nullable: nullable, + Type: pgType, + } + + kind := postgresOIDToQValueKind(oid, customTypeMap, newMap, hushWarnOID, logger) + derivedPgToChType, err := kind.ToDWHColumnType( + ctx, + env, + protos.DBType_CLICKHOUSE, + fd, + nullableEnabled, + ) + require.NoError(t, err) + directPgToChType := clickhouse.PostgresToClickHouseType(oid, fd.TypeModifier, fd.Nullable) + + require.Equal(t, derivedPgToChType, directPgToChType, "Mismatch type for oid=%d", oid) + } + +} diff --git a/flow/connectors/postgres/schema.go b/flow/connectors/postgres/schema.go index 07dab7375..2595c706f 100644 --- a/flow/connectors/postgres/schema.go +++ b/flow/connectors/postgres/schema.go @@ -100,10 +100,11 @@ func (c *PostgresConnector) GetTablesInSchema( func (c *PostgresConnector) GetColumns(ctx context.Context, schema string, table string) (*protos.TableColumnsResponse, error) { rows, err := c.conn.Query(ctx, `SELECT - DISTINCT attname AS column_name, - atttypid AS oid, - format_type(atttypid, atttypmod) AS data_type, - (pg_constraint.contype = 'p') AS is_primary_key + DISTINCT attname AS column_name, + atttypid AS oid, + atttypmod AS typmod, + format_type(atttypid, atttypmod) AS data_type, + (pg_constraint.contype = 'p') AS is_primary_key FROM pg_attribute JOIN pg_class ON pg_attribute.attrelid = pg_class.oid JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid @@ -122,9 +123,10 @@ func (c *PostgresConnector) GetColumns(ctx context.Context, schema string, table columns, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (*protos.ColumnsItem, error) { var columnName pgtype.Text var oid uint32 + var typmod int32 var datatype pgtype.Text var isPkey pgtype.Bool - if err := rows.Scan(&columnName, &oid, &datatype, &isPkey); err != nil { + if err := rows.Scan(&columnName, &oid, &typmod, &datatype, &isPkey); err != nil { return nil, err } return &protos.ColumnsItem{ @@ -132,6 +134,12 @@ func (c *PostgresConnector) GetColumns(ctx context.Context, schema string, table Type: datatype.String, IsKey: isPkey.Bool, Qkind: string(c.postgresOIDToQValueKind(oid, c.customTypeMapping)), + TypeInfo: &protos.ColumnsItem_PostgresTypeInfo{ + PostgresTypeInfo: &protos.PostgresTypeInfo{ + Oid: oid, + Typmod: typmod, + }, + }, }, nil }) if err != nil { diff --git a/flow/datatypes/numeric.go b/flow/datatypes/numeric.go index 8b942e4f6..10e76502d 100644 --- a/flow/datatypes/numeric.go +++ b/flow/datatypes/numeric.go @@ -1,5 +1,7 @@ package datatypes +import "github.com/PeerDB-io/peerdb/flow/shared" + const ( // defaults PeerDBBigQueryScale = 20 @@ -7,7 +9,6 @@ const ( PeerDBClickHouseScale = 38 PeerDBClickHouseMaxPrecision = 76 - VARHDRSZ = 4 ) type WarehouseNumericCompatibility interface { @@ -84,20 +85,7 @@ func MakeNumericTypmod(precision int32, scale int32) int32 { if precision == 0 && scale == 0 { return -1 } - return (precision << 16) | (scale & 0x7ff) + VARHDRSZ -} - -// This is to reverse what make_numeric_typmod of Postgres does: -// https://github.com/postgres/postgres/blob/21912e3c0262e2cfe64856e028799d6927862563/src/backend/utils/adt/numeric.c#L897 -func ParseNumericTypmod(typmod int32) (int16, int16) { - if typmod == -1 { - return 0, 0 - } - - offsetMod := typmod - VARHDRSZ - precision := int16((offsetMod >> 16) & 0x7FFF) - scale := int16(offsetMod & 0x7FFF) - return precision, scale + return (precision << 16) | (scale & 0x7ff) + shared.VARHDRSZ } func GetNumericTypeForWarehouse(typmod int32, warehouseNumeric WarehouseNumericCompatibility) (int16, int16) { @@ -105,7 +93,7 @@ func GetNumericTypeForWarehouse(typmod int32, warehouseNumeric WarehouseNumericC return warehouseNumeric.DefaultPrecisionAndScale() } - precision, scale := ParseNumericTypmod(typmod) + precision, scale := shared.ParseNumericTypmod(typmod) return GetNumericTypeForWarehousePrecisionScale(precision, scale, warehouseNumeric) } diff --git a/flow/model/qvalue/kind.go b/flow/model/qvalue/kind.go index d6cdeb562..d16bd8126 100644 --- a/flow/model/qvalue/kind.go +++ b/flow/model/qvalue/kind.go @@ -8,6 +8,7 @@ import ( "github.com/PeerDB-io/peerdb/flow/datatypes" "github.com/PeerDB-io/peerdb/flow/generated/protos" "github.com/PeerDB-io/peerdb/flow/internal" + "github.com/PeerDB-io/peerdb/flow/shared" ) type QValueKind string @@ -171,7 +172,7 @@ func getClickHouseTypeForNumericColumn(ctx context.Context, env map[string]strin if numericAsStringEnabled { return "String", nil } - } else if rawPrecision, _ := datatypes.ParseNumericTypmod(column.TypeModifier); rawPrecision > datatypes.PeerDBClickHouseMaxPrecision { + } else if rawPrecision, _ := shared.ParseNumericTypmod(column.TypeModifier); rawPrecision > datatypes.PeerDBClickHouseMaxPrecision { return "String", nil } precision, scale := datatypes.GetNumericTypeForWarehouse(column.TypeModifier, datatypes.ClickHouseNumericCompatibility{}) diff --git a/flow/shared/clickhouse/type_conversion.go b/flow/shared/clickhouse/type_conversion.go new file mode 100644 index 000000000..6c3f917e5 --- /dev/null +++ b/flow/shared/clickhouse/type_conversion.go @@ -0,0 +1,101 @@ +package clickhouse + +// the type conversion represents a mapping from postgres -> clickhouse + +import ( + "fmt" + + "github.com/jackc/pgx/v5/pgtype" + + "github.com/PeerDB-io/peerdb/flow/shared" +) + +const ( + ClickHouseMaxPrecision = 76 + ClickHouseMaxScale = 38 +) + +var PostgresToClickHouseTypeMap = map[uint32]string{ + pgtype.BoolOID: "Bool", + pgtype.Int2OID: "Int16", + pgtype.Int4OID: "Int32", + pgtype.Int8OID: "Int64", + pgtype.Float4OID: "Float32", + pgtype.Float8OID: "Float64", + pgtype.QCharOID: "FixedString(1)", + pgtype.TextOID: "String", + pgtype.VarcharOID: "String", + pgtype.BPCharOID: "String", + pgtype.ByteaOID: "String", + pgtype.JSONOID: "String", + pgtype.JSONBOID: "String", + pgtype.UUIDOID: "UUID", + pgtype.TimeOID: "DateTime64(6)", + pgtype.DateOID: "Date32", + pgtype.CIDROID: "String", + pgtype.MacaddrOID: "String", + pgtype.InetOID: "String", + pgtype.TimestampOID: "DateTime64(6)", + pgtype.TimestamptzOID: "DateTime64(6)", + pgtype.TimetzOID: "DateTime64(6)", + pgtype.Int2ArrayOID: "Array(Int16)", + pgtype.Int4ArrayOID: "Array(Int32)", + pgtype.Int8ArrayOID: "Array(Int64)", + pgtype.PointOID: "String", + pgtype.Float4ArrayOID: "Array(Float32)", + pgtype.Float8ArrayOID: "Array(Float64)", + pgtype.BoolArrayOID: "Array(Bool)", + pgtype.DateArrayOID: "Array(Date)", + pgtype.TimestampArrayOID: "Array(DateTime64(6))", + pgtype.TimestamptzArrayOID: "Array(DateTime64(6))", + pgtype.UUIDArrayOID: "Array(UUID)", + pgtype.TextArrayOID: "Array(String)", + pgtype.VarcharArrayOID: "Array(String)", + pgtype.BPCharArrayOID: "Array(String)", + pgtype.JSONArrayOID: "String", + pgtype.JSONBArrayOID: "String", + pgtype.IntervalOID: "String", + pgtype.TstzrangeOID: "String", +} + +var PostgresToSupportedClickHouseTypesMap = map[uint32][]string{ + pgtype.NumericOID: {"String"}, +} + +// TODO: handle nullable +// TODO: handle custom types +func PostgresToClickHouseType(oid uint32, typmod int32, nullable bool) string { + var chType string + + if oid == pgtype.NumericOID { + chType = getClickHouseTypeForNumericColumn(typmod) + } else if t, ok := PostgresToClickHouseTypeMap[oid]; ok { + chType = t + } else { + chType = "String" + } + + return chType +} + +func PostgresToSupportedClickHouseTypes(oid uint32, typmod int32, nullable bool) []string { + chTypes := []string{PostgresToClickHouseType(oid, typmod, nullable)} + if types, ok := PostgresToSupportedClickHouseTypesMap[oid]; ok { + chTypes = append(chTypes, types...) + } + return chTypes +} + +// copied and modified from flow/model/qvalue/kind.go +func getClickHouseTypeForNumericColumn(typmod int32) string { + if typmod == -1 { + return fmt.Sprintf("Decimal(%d, %d)", ClickHouseMaxPrecision, ClickHouseMaxScale) + } + + precision, scale := shared.ParseNumericTypmod(typmod) + if precision > ClickHouseMaxPrecision { + return "String" + } + + return fmt.Sprintf("Decimal(%d, %d)", precision, scale) +} diff --git a/flow/shared/mysql/types.go b/flow/shared/mysql/types.go new file mode 100644 index 000000000..b0843023d --- /dev/null +++ b/flow/shared/mysql/types.go @@ -0,0 +1 @@ +package mysql diff --git a/flow/shared/postgres.go b/flow/shared/postgres.go index 45a9bc76f..7aadb8294 100644 --- a/flow/shared/postgres.go +++ b/flow/shared/postgres.go @@ -337,3 +337,18 @@ func ParsePgArrayToStringSlice(data []byte, delim byte) []string { } return result } + +const VARHDRSZ = 4 + +// This is to reverse what make_numeric_typmod of Postgres does: +// https://github.com/postgres/postgres/blob/21912e3c0262e2cfe64856e028799d6927862563/src/backend/utils/adt/numeric.c#L897 +func ParseNumericTypmod(typmod int32) (int16, int16) { + if typmod == -1 { + return 0, 0 + } + + offsetMod := typmod - VARHDRSZ + precision := int16((offsetMod >> 16) & 0x7FFF) + scale := int16(offsetMod & 0x7FFF) + return precision, scale +} diff --git a/protos/route.proto b/protos/route.proto index e6959657f..beabbf800 100644 --- a/protos/route.proto +++ b/protos/route.proto @@ -181,11 +181,22 @@ message TableColumnsRequest { string table_name = 3; } +message PostgresTypeInfo { + uint32 oid = 1; + int32 typmod = 2; +} + +message NullTypeInfo { } + message ColumnsItem { string name = 1; string type = 2; bool is_key = 3; string qkind = 4; + oneof type_info { + PostgresTypeInfo postgres_type_info = 5; + NullTypeInfo null_type_info = 6; + } } message TableColumnsResponse {