Skip to content

Commit 701ce75

Browse files
authored
Drop NULL columns from INSERT INTO (#68)
1 parent 92f1864 commit 701ce75

File tree

6 files changed

+65
-9
lines changed

6 files changed

+65
-9
lines changed

deploy/samples/subscriptions.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ kind: Subscription
44
metadata:
55
name: names
66
spec:
7-
sql: SELECT NAME, NAME AS KEY FROM DATAGEN.PERSON
7+
sql: SELECT NAME, NULL AS KEY FROM DATAGEN.PERSON
88
database: RAWKAFKA
99

1010

hoptimator-catalog/src/main/java/com/linkedin/hoptimator/catalog/DataType.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
public enum DataType {
1616

1717
VARCHAR(x -> x.createTypeWithNullability(x.createSqlType(SqlTypeName.VARCHAR), true)),
18-
VARCHAR_NOT_NULL(x -> x.createTypeWithNullability(x.createSqlType(SqlTypeName.VARCHAR), false));
18+
VARCHAR_NOT_NULL(x -> x.createTypeWithNullability(x.createSqlType(SqlTypeName.VARCHAR), false)),
19+
NULL(x -> x.createSqlType(SqlTypeName.NULL));
1920

2021
public static final RelDataTypeFactory DEFAULT_TYPE_FACTORY = new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
2122
private final RelProtoDataType protoType;

hoptimator-catalog/src/main/java/com/linkedin/hoptimator/catalog/ScriptImplementor.java

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.linkedin.hoptimator.catalog;
22

3+
import org.apache.calcite.plan.RelOptUtil;
34
import org.apache.calcite.rel.RelNode;
45
import org.apache.calcite.rel.type.RelDataType;
56
import org.apache.calcite.rel.type.RelDataTypeFactory;
@@ -27,11 +28,13 @@
2728
import org.apache.calcite.sql.parser.SqlParserPos;
2829
import org.apache.calcite.sql.pretty.SqlPrettyWriter;
2930
import org.apache.calcite.sql.type.SqlTypeFactoryImpl;
31+
import org.apache.calcite.sql.type.SqlTypeName;
3032
import org.apache.calcite.sql.util.SqlShuttle;
3133

3234
import java.util.Map;
3335
import java.util.List;
3436
import java.util.Arrays;
37+
import java.util.ArrayList;
3538
import java.util.stream.Collectors;
3639

3740
/**
@@ -171,6 +174,7 @@ public SqlNode visit(SqlCall call) {
171174
*
172175
* N.B. the following magic:
173176
* - field 'PRIMARY_KEY' is treated as a PRIMARY KEY
177+
* - NULL fields are promoted to BYTES
174178
*/
175179
class ConnectorImplementor implements ScriptImplementor {
176180
private final String database;
@@ -228,7 +232,12 @@ public void implement(SqlWriter w) {
228232
}
229233
}
230234

231-
/** Implements an INSERT INTO statement */
235+
/** Implements an INSERT INTO statement.
236+
*
237+
* N.B. the following magic:
238+
* - NULL columns (e.g. `NULL AS KEY`) are elided from the pipeline
239+
*
240+
* */
232241
class InsertImplementor implements ScriptImplementor {
233242
private final String database;
234243
private final String name;
@@ -245,11 +254,24 @@ public void implement(SqlWriter w) {
245254
w.keyword("INSERT INTO");
246255
(new CompoundIdentifierImplementor(database, name)).implement(w);
247256
SqlWriter.Frame frame1 = w.startList("(", ")");
248-
(new ColumnListImplementor(relNode.getRowType())).implement(w);
257+
RelNode project = dropNullFields(relNode);
258+
(new ColumnListImplementor(project.getRowType())).implement(w);
249259
w.endList(frame1);
250-
(new QueryImplementor(relNode)).implement(w);
260+
(new QueryImplementor(project)).implement(w);
251261
w.literal(";");
252262
}
263+
264+
private static RelNode dropNullFields(RelNode relNode) {
265+
List<Integer> cols = new ArrayList<>();
266+
int i = 0;
267+
for (RelDataTypeField field : relNode.getRowType().getFieldList()) {
268+
if (!field.getType().getSqlTypeName().equals(SqlTypeName.NULL)) {
269+
cols.add(i);
270+
}
271+
i++;
272+
}
273+
return RelOptUtil.createProject(relNode, cols);
274+
}
253275
}
254276

255277
/** Implements a CREATE DATABASE IF NOT EXISTS statement */
@@ -288,7 +310,11 @@ public void implement(SqlWriter w) {
288310
}
289311
}
290312

291-
/** Implements row type specs, e.g. `NAME VARCHAR(20), AGE INTEGER` */
313+
/** Implements row type specs, e.g. `NAME VARCHAR(20), AGE INTEGER`.
314+
*
315+
* N.B. the following magic:
316+
* - NULL fields are promoted to BYTES
317+
*/
292318
class RowTypeSpecImplementor implements ScriptImplementor {
293319
private final RelDataType dataType;
294320

@@ -309,7 +335,11 @@ public void implement(SqlWriter w) {
309335
for (int i = 0; i < fieldNames.size(); i++) {
310336
w.sep(",");
311337
fieldNames.get(i).unparse(w, 0, 0);
312-
fieldTypes.get(i).unparse(w, 0, 0);
338+
if (fieldTypes.get(i).getTypeName().getSimple().equals("NULL")) {
339+
w.literal("BYTES"); // promote NULL fields to BYTES
340+
} else {
341+
fieldTypes.get(i).unparse(w, 0, 0);
342+
}
313343
}
314344
}
315345

hoptimator-catalog/src/test/java/com/linkedin/hoptimator/catalog/ScriptImplementorTest.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,4 +34,27 @@ public void implementsFlinkCreateTableDDL() {
3434
assertTrue(out, out.contains("'topic'='topic1'"));
3535
assertFalse(out, out.contains("Row"));
3636
}
37+
38+
@Test
39+
public void magicPrimaryKey() {
40+
SqlWriter w = new SqlPrettyWriter();
41+
RelDataType rowType = DataType.struct().with("F1", DataType.VARCHAR)
42+
.with("PRIMARY_KEY", DataType.VARCHAR).rel();
43+
HopTable table = new HopTable("DATABASE", "TABLE1", rowType, ConfigProvider.empty().config("x"));
44+
table.implement(w);
45+
String out = w.toString();
46+
assertTrue(out, out.contains("PRIMARY KEY (PRIMARY_KEY)"));
47+
}
48+
49+
@Test
50+
public void magicNullFields() {
51+
SqlWriter w = new SqlPrettyWriter();
52+
RelDataType rowType = DataType.struct().with("F1", DataType.VARCHAR)
53+
.with("KEY", DataType.NULL).rel();
54+
HopTable table = new HopTable("DATABASE", "TABLE1", rowType, ConfigProvider.empty().config("x"));
55+
table.implement(w);
56+
String out = w.toString();
57+
assertTrue(out, out.contains("\"KEY\" BYTES")); // NULL fields are promoted to BYTES.
58+
assertFalse(out, out.contains("\"KEY\" NULL")); // Without magic, this is what you'd get.
59+
}
3760
}

hoptimator-kafka-adapter/src/main/java/com/linkedin/hoptimator/catalog/kafka/RawKafkaSchemaFactory.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,11 @@ public Schema create(SchemaPlus parentSchema, String name, Map<String, Object> o
3232
ConfigProvider connectorConfigProvider = ConfigProvider.from(clientConfig)
3333
.withPrefix("properties.")
3434
.with("connector", "kafka")
35-
.with("key.format", "csv")
35+
.with("key.format", "raw")
3636
.with("key.fields", "KEY")
3737
.with("value.format", "csv")
3838
.with("value.fields-include", "EXCEPT_KEY")
39+
.with("scan.startup.mode", "earliest-offset")
3940
.with("topic", x -> x);
4041
TableLister tableLister = () -> {
4142
AdminClient client = AdminClient.create(clientConfig);

hoptimator-planner/src/main/java/com/linkedin/hoptimator/planner/PipelineRel.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,9 @@ public ScriptImplementor query() {
8080
/** Script ending in INSERT INTO ... */
8181
public ScriptImplementor insertInto(HopTable sink) {
8282
RelOptUtil.eq(sink.name(), sink.rowType(), "subscription", rowType(), Litmus.THROW);
83+
RelNode castRel = RelOptUtil.createCastRel(relNode, sink.rowType(), true);
8384
return script.database(sink.database()).with(sink)
84-
.insert(sink.database(), sink.name(), relNode);
85+
.insert(sink.database(), sink.name(), castRel);
8586
}
8687

8788
/** Add any resources, SQL, DDL etc required to access the table. */

0 commit comments

Comments
 (0)