Skip to content

Commit ab7c9fe

Browse files
committed
Support VGI protocol 1.5 scan function schemas
1 parent ad4ab00 commit ab7c9fe

7 files changed

Lines changed: 130 additions & 33 deletions

File tree

vgi/src/main/java/farm/query/vgi/Worker.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ public final class Worker {
5050
* and {@link #globalFunctionPrefix(String)}. A worker that opts out still
5151
* carries the fields (empty list, empty prefix): the extension matches the
5252
* response schema exactly. */
53-
public static final String VGI_PROTOCOL_VERSION = "1.4.0";
53+
public static final String VGI_PROTOCOL_VERSION = "1.5.0";
5454

5555
private String catalogName = "vgi";
5656
private String catalogComment = "";
@@ -519,6 +519,30 @@ public String schemaOf(Object fn) {
519519
return h == null || h.schemaName() == null ? defaultSchema : h.schemaName();
520520
}
521521

522+
/**
523+
* Resolve the schema containing a named table function. The table's schema
524+
* wins when that function is registered there; otherwise a single
525+
* unambiguous registration is returned. Native DuckDB functions and
526+
* ambiguous names return {@code null}.
527+
*
528+
* @param functionName the function named by a scan result
529+
* @param tableSchema the schema containing the table being resolved
530+
* @param catalogName the auxiliary catalog owner, or {@code null} for this worker
531+
* @return the authoritative function schema, or {@code null}
532+
*/
533+
public String resolveTableFunctionSchema(
534+
String functionName, String tableSchema, String catalogName) {
535+
java.util.LinkedHashSet<String> homes = new java.util.LinkedHashSet<>();
536+
for (TableFunction fn : tables) {
537+
if (!fn.name().equals(functionName)
538+
|| !java.util.Objects.equals(catalogOf(fn), catalogName)) continue;
539+
String schema = schemaOf(fn);
540+
if (schema.equalsIgnoreCase(tableSchema)) return schema;
541+
homes.add(schema);
542+
}
543+
return homes.size() == 1 ? homes.iterator().next() : null;
544+
}
545+
522546
/**
523547
* The auxiliary catalog {@code fn} is declared in, or {@code null} when it
524548
* belongs to this worker's own catalog. Ownership is always explicit — a

vgi/src/main/java/farm/query/vgi/catalog/ScanBranch.java

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
* no locations is rejected
3737
* @param formatOptions format branch only — reader options, which BECOME the
3838
* reader's named arguments. Empty for the other kinds
39+
* @param schemaName function branch only — schema containing the named VGI
40+
* function; {@code null} for native/ambiguous functions
3941
*/
4042
public record ScanBranch(
4143
String functionName,
@@ -48,7 +50,28 @@ public record ScanBranch(
4850
String sourceTable,
4951
String formatName,
5052
List<String> formatLocations,
51-
Map<String, Object> formatOptions) {
53+
Map<String, Object> formatOptions,
54+
String schemaName) {
55+
56+
/**
57+
* Source-compatible constructor for pre-1.5 callers. The worker resolves
58+
* the function schema before serializing the branch.
59+
*/
60+
public ScanBranch(
61+
String functionName,
62+
List<Object> positional,
63+
Map<String, Object> named,
64+
String branchFilter,
65+
boolean writable,
66+
String sourceCatalog,
67+
String sourceSchema,
68+
String sourceTable,
69+
String formatName,
70+
List<String> formatLocations,
71+
Map<String, Object> formatOptions) {
72+
this(functionName, positional, named, branchFilter, writable, sourceCatalog,
73+
sourceSchema, sourceTable, formatName, formatLocations, formatOptions, null);
74+
}
5275

5376
/**
5477
* Validates the branch and defensively copies the collections, normalizing
@@ -118,7 +141,7 @@ public record ScanBranch(
118141
public static ScanBranch format(String formatName, List<String> locations,
119142
Map<String, Object> options) {
120143
return new ScanBranch("", List.of(), Map.of(), null, false, null, null, null,
121-
formatName, locations, options);
144+
formatName, locations, options, null);
122145
}
123146

124147
/**
@@ -129,7 +152,7 @@ public static ScanBranch format(String formatName, List<String> locations,
129152
* @return the branch
130153
*/
131154
public static ScanBranch of(String functionName, Object... positional) {
132-
return new ScanBranch(functionName, List.of(positional), Map.of(), null, false, null, null, null, null, null, null);
155+
return new ScanBranch(functionName, List.of(positional), Map.of(), null, false, null, null, null, null, null, null, null);
133156
}
134157

135158
/**
@@ -141,7 +164,7 @@ public static ScanBranch of(String functionName, Object... positional) {
141164
* @return the branch
142165
*/
143166
public static ScanBranch filtered(String functionName, String branchFilter, Object... positional) {
144-
return new ScanBranch(functionName, List.of(positional), Map.of(), branchFilter, false, null, null, null, null, null, null);
167+
return new ScanBranch(functionName, List.of(positional), Map.of(), branchFilter, false, null, null, null, null, null, null, null);
145168
}
146169

147170
/**
@@ -152,7 +175,7 @@ public static ScanBranch filtered(String functionName, String branchFilter, Obje
152175
* @return the writable branch
153176
*/
154177
public static ScanBranch writable(String functionName, Object... positional) {
155-
return new ScanBranch(functionName, List.of(positional), Map.of(), null, true, null, null, null, null, null, null);
178+
return new ScanBranch(functionName, List.of(positional), Map.of(), null, true, null, null, null, null, null, null, null);
156179
}
157180

158181
/**
@@ -169,6 +192,13 @@ public static ScanBranch writable(String functionName, Object... positional) {
169192
public static ScanBranch catalogTable(
170193
String sourceCatalog, String sourceSchema, String sourceTable, String branchFilter) {
171194
return new ScanBranch("", List.of(), Map.of(), branchFilter, false, sourceCatalog, sourceSchema,
172-
sourceTable, null, null, null);
195+
sourceTable, null, null, null, null);
196+
}
197+
198+
/** Return this branch with an authoritative function schema. */
199+
public ScanBranch withSchemaName(String schema) {
200+
return new ScanBranch(functionName, positional, named, branchFilter, writable,
201+
sourceCatalog, sourceSchema, sourceTable, formatName, formatLocations,
202+
formatOptions, schema);
173203
}
174204
}

vgi/src/main/java/farm/query/vgi/internal/ScanBranchesResultSerializer.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,8 @@ private ScanBranchesResultSerializer() {}
5454
new Field("format_name", new FieldType(true, UTF8, null), null),
5555
new Field("format_locations", new FieldType(true, new ArrowType.List(), null),
5656
List.of(new Field("item", new FieldType(true, UTF8, null), null))),
57-
new Field("format_options", new FieldType(true, BINARY, null), null)));
57+
new Field("format_options", new FieldType(true, BINARY, null), null),
58+
new Field("schema_name", new FieldType(true, UTF8, null), null)));
5859

5960
private static final Schema RESULT_SCHEMA = new Schema(List.of(
6061
new Field("branches", new FieldType(false, new ArrowType.List(), null),
@@ -126,6 +127,7 @@ private static byte[] encodeBranch(ScanBranch b, BufferAllocator alloc) {
126127
// names, because an option value may be any Arrow type.
127128
fo.setSafe(0, ScanFunctionResultEncoder.encodeArguments(List.of(), b.formatOptions()));
128129
}
130+
setNullableString(root, "schema_name", b.schemaName());
129131
root.setRowCount(1);
130132
return writeStream(root);
131133
} catch (Exception e) {

vgi/src/main/java/farm/query/vgi/internal/ScanFunctionResultEncoder.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,17 +43,20 @@ private ScanFunctionResultEncoder() {}
4343
* @param positional positional scan arguments (may be {@code null})
4444
* @param named named scan arguments (may be {@code null})
4545
* @param requiredExtensions DuckDB extension names the scan depends on (may be {@code null})
46+
* @param schemaName schema containing the function, or {@code null}
4647
* @return the 1-row IPC stream bytes
4748
*/
4849
public static byte[] encode(String functionName, List<Object> positional,
49-
Map<String, Object> named, List<String> requiredExtensions) {
50+
Map<String, Object> named, List<String> requiredExtensions,
51+
String schemaName) {
5052
BufferAllocator alloc = Allocators.root();
5153
Schema schema = new Schema(List.of(
5254
new Field("function_name", new FieldType(false, UTF8, null), null),
5355
new Field("arguments", new FieldType(false, BINARY, null), null),
5456
new Field("required_extensions",
5557
new FieldType(false, new ArrowType.List(), null),
56-
List.of(new Field("item", new FieldType(true, UTF8, null), null)))));
58+
List.of(new Field("item", new FieldType(true, UTF8, null), null))),
59+
new Field("schema_name", new FieldType(true, UTF8, null), null)));
5760

5861
byte[] argsBytes = encodeArguments(positional, named, alloc);
5962

@@ -69,6 +72,9 @@ public static byte[] encode(String functionName, List<Object> positional,
6972
}
7073
w.endList();
7174
w.setValueCount(1);
75+
VarCharVector schemaVector = (VarCharVector) root.getVector("schema_name");
76+
if (schemaName == null) schemaVector.setNull(0);
77+
else schemaVector.setSafe(0, new Text(schemaName));
7278
root.setRowCount(1);
7379
ByteArrayOutputStream baos = new ByteArrayOutputStream();
7480
try (ArrowStreamWriter sw = new ArrowStreamWriter(root, null, Channels.newChannel(baos))) {

0 commit comments

Comments
 (0)