Skip to content

Commit 0dd02fc

Browse files
authored
implement backend for fetching type conversions (#2989)
Implement the backend to support [type conversion frontend](#2987)
1 parent e65a039 commit 0dd02fc

6 files changed

Lines changed: 132 additions & 45 deletions

File tree

flow/cmd/peer_data.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"google.golang.org/protobuf/types/descriptorpb"
1414

1515
"github.com/PeerDB-io/peerdb/flow/connectors"
16+
connclickhouse "github.com/PeerDB-io/peerdb/flow/connectors/clickhouse"
1617
connpostgres "github.com/PeerDB-io/peerdb/flow/connectors/postgres"
1718
"github.com/PeerDB-io/peerdb/flow/generated/protos"
1819
"github.com/PeerDB-io/peerdb/flow/internal"
@@ -176,6 +177,13 @@ func (h *FlowRequestHandler) GetColumns(
176177
return conn.GetColumns(ctx, req.SchemaName, req.TableName)
177178
}
178179

180+
func (h *FlowRequestHandler) GetColumnsTypeConversion(
181+
ctx context.Context,
182+
req *protos.ColumnsTypeConversionRequest,
183+
) (*protos.ColumnsTypeConversionResponse, error) {
184+
return connclickhouse.GetColumnsTypeConversion()
185+
}
186+
179187
func (h *FlowRequestHandler) GetSlotInfo(
180188
ctx context.Context,
181189
req *protos.PostgresPeerActivityInfoRequest,

flow/connectors/clickhouse/qrep_avro_sync.go

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -364,47 +364,3 @@ func (s *ClickHouseAvroSyncMethod) writeToAvroFile(
364364

365365
return avroFile, nil
366366
}
367-
368-
// add more supported type conversions as needed
369-
var supportedDestinationTypes = map[string][]qvalue.TypeConversion{
370-
"String": {qvalue.NewTypeConversion(
371-
qvalue.NumericToStringSchemaConversion,
372-
qvalue.NumericToStringValueConversion,
373-
)},
374-
}
375-
376-
func findTypeConversions(schema qvalue.QRecordSchema, columns []*protos.ColumnSetting) map[string]qvalue.TypeConversion {
377-
typeConversions := make(map[string]qvalue.TypeConversion)
378-
379-
colNameToType := make(map[string]qvalue.QValueKind, len(schema.Fields))
380-
for _, field := range schema.Fields {
381-
colNameToType[field.Name] = field.Type
382-
}
383-
384-
for _, col := range columns {
385-
colType, exist := colNameToType[col.SourceName]
386-
if !exist {
387-
continue
388-
}
389-
conversions, exist := supportedDestinationTypes[col.DestinationType]
390-
if !exist {
391-
continue
392-
}
393-
for _, conversion := range conversions {
394-
if conversion.FromKind() == colType {
395-
typeConversions[col.SourceName] = conversion
396-
}
397-
}
398-
}
399-
400-
return typeConversions
401-
}
402-
403-
func applyTypeConversions(schema qvalue.QRecordSchema, typeConversions map[string]qvalue.TypeConversion) qvalue.QRecordSchema {
404-
for i, field := range schema.Fields {
405-
if conversion, exist := typeConversions[field.Name]; exist {
406-
schema.Fields[i] = conversion.SchemaConversion(field)
407-
}
408-
}
409-
return schema
410-
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package connclickhouse
2+
3+
import (
4+
"github.com/PeerDB-io/peerdb/flow/generated/protos"
5+
"github.com/PeerDB-io/peerdb/flow/model/qvalue"
6+
)
7+
8+
/*
9+
This file handles the mapping for ClickHouse destination types and
10+
their corresponding TypeConversion implementations. A TypeConversion
11+
object contains two functions: one for schema conversion (QField) and
12+
one for value conversion (QValue). This allows the avro writer to
13+
stage the schema/data in the converted type format, and therefore
14+
successfully uploaded to the desired destination type in ClickHouse.
15+
16+
To add a type conversion:
17+
(1) In flow/model/qvalue/type_converter.go:
18+
- implement a SchemaConversionFn interface to convert the QField type
19+
- implement a ValueConversionFn interface to convert the QValue data
20+
21+
(2) Add the new conversion to the `supportedDestinationTypes` map here
22+
(if destination type doesn't exist, create a new map entry for it).
23+
24+
The GetColumnsTypeConversion function returns the full list of supported
25+
type conversions. Note that the source types are QValueKind, this allows
26+
the implementation to be source-connector agnostic.
27+
*/
28+
29+
var supportedDestinationTypes = map[string][]qvalue.TypeConversion{
30+
"String": {qvalue.NewTypeConversion(
31+
qvalue.NumericToStringSchemaConversion,
32+
qvalue.NumericToStringValueConversion,
33+
)},
34+
}
35+
36+
func GetColumnsTypeConversion() (*protos.ColumnsTypeConversionResponse, error) {
37+
res := make([]*protos.ColumnsTypeConversion, 0)
38+
for qkind, destTypes := range listSupportedTypeConversions() {
39+
res = append(res, &protos.ColumnsTypeConversion{
40+
Qkind: string(qkind),
41+
DestinationTypes: destTypes,
42+
})
43+
}
44+
return &protos.ColumnsTypeConversionResponse{
45+
Conversions: res,
46+
}, nil
47+
}
48+
49+
func listSupportedTypeConversions() map[qvalue.QValueKind][]string {
50+
typeConversions := make(map[qvalue.QValueKind][]string)
51+
52+
for dstType, l := range supportedDestinationTypes {
53+
for _, conversion := range l {
54+
typeConversions[conversion.FromKind()] = append(typeConversions[conversion.FromKind()], dstType)
55+
}
56+
}
57+
return typeConversions
58+
}
59+
60+
func findTypeConversions(schema qvalue.QRecordSchema, columns []*protos.ColumnSetting) map[string]qvalue.TypeConversion {
61+
typeConversions := make(map[string]qvalue.TypeConversion)
62+
63+
colNameToType := make(map[string]qvalue.QValueKind, len(schema.Fields))
64+
for _, field := range schema.Fields {
65+
colNameToType[field.Name] = field.Type
66+
}
67+
68+
for _, col := range columns {
69+
colType, exist := colNameToType[col.SourceName]
70+
if !exist {
71+
continue
72+
}
73+
conversions, exist := supportedDestinationTypes[col.DestinationType]
74+
if !exist {
75+
continue
76+
}
77+
for _, conversion := range conversions {
78+
if conversion.FromKind() == colType {
79+
typeConversions[col.SourceName] = conversion
80+
}
81+
}
82+
}
83+
84+
return typeConversions
85+
}
86+
87+
func applyTypeConversions(schema qvalue.QRecordSchema, typeConversions map[string]qvalue.TypeConversion) qvalue.QRecordSchema {
88+
for i, field := range schema.Fields {
89+
if conversion, exist := typeConversions[field.Name]; exist {
90+
schema.Fields[i] = conversion.SchemaConversion(field)
91+
}
92+
}
93+
return schema
94+
}

flow/connectors/mysql/schema.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,10 +95,15 @@ func (c *MySqlConnector) GetColumns(ctx context.Context, schema string, table st
9595
if err != nil {
9696
return nil, err
9797
}
98+
qkind, err := qkindFromMysqlColumnType(columnType)
99+
if err != nil {
100+
return nil, err
101+
}
98102
columns = append(columns, &protos.ColumnsItem{
99103
Name: columnName,
100104
Type: columnType,
101105
IsKey: columnKey == "PRI",
106+
Qkind: string(qkind),
102107
})
103108
}
104109
return &protos.TableColumnsResponse{Columns: columns}, nil

flow/connectors/postgres/schema.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ func (c *PostgresConnector) GetTablesInSchema(
101101
func (c *PostgresConnector) GetColumns(ctx context.Context, schema string, table string) (*protos.TableColumnsResponse, error) {
102102
rows, err := c.conn.Query(ctx, `SELECT
103103
DISTINCT attname AS column_name,
104+
atttypid AS oid,
104105
format_type(atttypid, atttypmod) AS data_type,
105106
(pg_constraint.contype = 'p') AS is_primary_key
106107
FROM pg_attribute
@@ -120,15 +121,17 @@ func (c *PostgresConnector) GetColumns(ctx context.Context, schema string, table
120121

121122
columns, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (*protos.ColumnsItem, error) {
122123
var columnName pgtype.Text
124+
var oid uint32
123125
var datatype pgtype.Text
124126
var isPkey pgtype.Bool
125-
if err := rows.Scan(&columnName, &datatype, &isPkey); err != nil {
127+
if err := rows.Scan(&columnName, &oid, &datatype, &isPkey); err != nil {
126128
return nil, err
127129
}
128130
return &protos.ColumnsItem{
129131
Name: columnName.String,
130132
Type: datatype.String,
131133
IsKey: isPkey.Bool,
134+
Qkind: string(c.postgresOIDToQValueKind(oid, c.customTypeMapping)),
132135
}, nil
133136
})
134137
if err != nil {

protos/route.proto

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,11 +185,26 @@ message ColumnsItem {
185185
string name = 1;
186186
string type = 2;
187187
bool is_key = 3;
188+
string qkind = 4;
188189
}
190+
189191
message TableColumnsResponse {
190192
repeated ColumnsItem columns = 1;
191193
}
192194

195+
message ColumnsTypeConversionRequest {
196+
string destination_peer_type = 1;
197+
}
198+
199+
message ColumnsTypeConversion {
200+
string qkind = 1;
201+
repeated string destination_types = 2;
202+
}
203+
204+
message ColumnsTypeConversionResponse {
205+
repeated ColumnsTypeConversion conversions = 1;
206+
}
207+
193208
message PostgresPeerActivityInfoRequest { string peer_name = 1; }
194209

195210
message PeerInfoRequest { string peer_name = 1; }
@@ -560,6 +575,12 @@ service FlowService {
560575
};
561576
}
562577

578+
rpc GetColumnsTypeConversion(ColumnsTypeConversionRequest) returns (ColumnsTypeConversionResponse) {
579+
option (google.api.http) = {
580+
get : "/v1/peers/columns/all_type_conversions",
581+
};
582+
}
583+
563584
rpc GetSlotInfo(PostgresPeerActivityInfoRequest) returns (PeerSlotResponse) {
564585
option (google.api.http) = {
565586
get : "/v1/peers/slots/{peer_name}"

0 commit comments

Comments
 (0)