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
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ Diagram(
Sequence(
NonTerminal('columnName'),
NonTerminal('columnType'),
Optional(
Choice(0,
Terminal('INVISIBLE'),
Terminal('VISIBLE')
)
)
),
Terminal(',')
),
Expand Down
80 changes: 80 additions & 0 deletions docs/sphinx/source/reference/sql_commands/DDL/CREATE/TABLE.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ must be declared as a ``SINGLE ROW ONLY`` table, in which case only one row can
This latter type of table can be useful for things like database configuration parameters, which the
application wants to inform its interpretation of all data in the database.

Columns can be marked as ``INVISIBLE`` to hide them from ``SELECT *`` queries while still allowing
explicit selection by name. This is useful for backward compatibility when adding new columns,
or for system/computed columns that should not appear in normal queries.

Syntax
======

Expand All @@ -26,6 +30,12 @@ Parameters
``columnType``
The associated :ref:`type <sql-types>` of the column

``INVISIBLE`` / ``VISIBLE``
Optional visibility modifier for a column:

- ``INVISIBLE``: Column is excluded from ``SELECT *`` queries but can be explicitly selected
- ``VISIBLE``: Column is included in ``SELECT *`` queries (default behavior)

``primaryKeyColumnName``
The name of the column to be part of the primary key of the ``TABLE``

Expand Down Expand Up @@ -109,3 +119,73 @@ Attempting to insert a second row in a :sql:`SINGLE ROW ONLY` table will result
* - :sql:`NULL`
- :json:`0.0`
- :json:`"X"`

Table with invisible columns
-----------------------------

Invisible columns are excluded from ``SELECT *`` but can be explicitly selected.
This is useful for adding columns without breaking existing queries that use ``SELECT *``.

.. code-block:: sql

CREATE SCHEMA TEMPLATE TEMP
CREATE TABLE T (
id BIGINT,
name STRING,
secret STRING INVISIBLE,
PRIMARY KEY(id)
)

-- On a schema that uses the above schema template
INSERT INTO T VALUES
(1, 'Alice', 'password123'),
(2, 'Bob', 'secret456');

-- SELECT * excludes invisible columns
SELECT * FROM T;

.. list-table::
:header-rows: 1

* - :sql:`id`
- :sql:`name`
* - :json:`1`
- :json:`"Alice"`
* - :json:`2`
- :json:`"Bob"`

.. code-block:: sql

-- Explicitly selecting invisible columns includes them
SELECT id, name, secret FROM T;

.. list-table::
:header-rows: 1

* - :sql:`id`
- :sql:`name`
- :sql:`secret`
* - :json:`1`
- :json:`"Alice"`
- :json:`"password123"`
* - :json:`2`
- :json:`"Bob"`
- :json:`"secret456"`

.. code-block:: sql

-- Invisible columns in subqueries become visible when explicitly selected
SELECT * FROM (SELECT id, name, secret FROM T) sub;

.. list-table::
:header-rows: 1

* - :sql:`id`
- :sql:`name`
- :sql:`secret`
* - :json:`1`
- :json:`"Alice"`
- :json:`"password123"`
* - :json:`2`
- :json:`"Bob"`
- :json:`"secret456"`
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ message FieldOptions {
optional int32 dimensions = 2 [default = 768];
}
optional VectorOptions vectorOptions = 4;
optional bool invisible = 5 [default = false];
}

extend google.protobuf.FieldOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,24 @@ public interface Column extends Metadata {
*/
DataType getDataType();

/**
* Returns whether this column is invisible.
* <p>
* Invisible columns are a SQL feature that hides columns from {@code SELECT *} queries
* while still allowing them to be explicitly selected by name.
* <p>
* Query behavior:
* <ul>
* <li>{@code SELECT * FROM table} - excludes invisible columns</li>
* <li>{@code SELECT col FROM table} - includes invisible column {@code col} if explicitly named</li>
* </ul>
*
* @return {@code true} if the column is invisible, {@code false} otherwise
*/
default boolean isInvisible() {
return false; // Default to visible for backward compatibility
}

@Override
default void accept(@Nonnull final Visitor visitor) {
visitor.visit(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1307,16 +1307,24 @@ public static class Field {

private final int index;

private Field(@Nonnull final String name, @Nonnull final DataType type, int index) {
private final boolean invisible;

private Field(@Nonnull final String name, @Nonnull final DataType type, int index, boolean invisible) {
Assert.thatUnchecked(index >= 0);
this.name = name;
this.type = type;
this.index = index;
this.invisible = invisible;
}

@Nonnull
public static Field from(@Nonnull final String name, @Nonnull final DataType type, int index) {
return new Field(name, type, index);
return new Field(name, type, index, false);
}

@Nonnull
public static Field from(@Nonnull final String name, @Nonnull final DataType type, int index, boolean invisible) {
return new Field(name, type, index, invisible);
}

@Nonnull
Expand All @@ -1333,8 +1341,12 @@ public int getIndex() {
return index;
}

public boolean isInvisible() {
return invisible;
}

private int computeHashCode() {
return Objects.hash(name, index, type);
return Objects.hash(name, index, type, invisible);
}

@Override
Expand All @@ -1354,6 +1366,7 @@ public boolean equals(Object other) {
final var otherField = (Field) other;
return name.equals(otherField.name) &&
index == otherField.index &&
invisible == otherField.invisible &&
type.equals(otherField.type);
}

Expand Down
6 changes: 5 additions & 1 deletion fdb-relational-core/src/main/antlr/RelationalParser.g4
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,11 @@ tableDefinition
;

columnDefinition
: colName=uid columnType ARRAY? columnConstraint?
: colName=uid columnType ARRAY? columnConstraint? columnVisibility?
;

columnVisibility
: VISIBLE | INVISIBLE
;

// this is not aligned with SQL standard, but it eliminates ambiguities related to necessating a lookahead of 1 to resolve
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public static DataType toRelationalType(@Nonnull final Type type) {
switch (typeCode) {
case RECORD:
final var record = (Type.Record) type;
final var columns = record.getFields().stream().map(field -> DataType.StructType.Field.from(field.getFieldName(), toRelationalType(field.getFieldType()), field.getFieldIndex())).collect(Collectors.toList());
final var columns = record.getFields().stream().map(field -> DataType.StructType.Field.from(field.getFieldName(), toRelationalType(field.getFieldType()), field.getFieldIndex(), false)).collect(Collectors.toList());
return DataType.StructType.from(record.getName() == null ? ProtoUtils.uniqueTypeName() : record.getName(), columns, record.isNullable());
case ARRAY:
final var asArray = (Type.Array) type;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,13 @@ public class RecordLayerColumn implements Column {

private final int index;

RecordLayerColumn(@Nonnull String name, @Nonnull DataType dataType, int index) {
private final boolean invisible;

RecordLayerColumn(@Nonnull String name, @Nonnull DataType dataType, int index, boolean invisible) {
this.name = name;
this.dataType = dataType;
this.index = index;
this.invisible = invisible;
}

@Nonnull
Expand All @@ -61,6 +64,11 @@ public int getIndex() {
return index;
}

@Override
public boolean isInvisible() {
return invisible;
}

@Override
public boolean equals(final Object object) {
if (this == object) {
Expand All @@ -70,17 +78,17 @@ public boolean equals(final Object object) {
return false;
}
final RecordLayerColumn that = (RecordLayerColumn)object;
return index == that.index && Objects.equals(name, that.name) && Objects.equals(dataType, that.dataType);
return index == that.index && invisible == that.invisible && Objects.equals(name, that.name) && Objects.equals(dataType, that.dataType);
}

@Override
public int hashCode() {
return Objects.hash(name, dataType, index);
return Objects.hash(name, dataType, index, invisible);
}

@Override
public String toString() {
return name + ": " + dataType + " = " + index;
return name + ": " + dataType + " = " + index + (invisible ? " (invisible)" : "");
}

public static final class Builder {
Expand All @@ -89,8 +97,11 @@ public static final class Builder {

private int index;

private boolean invisible;

private Builder() {
this.index = -1;
this.invisible = false;
}

@Nonnull
Expand All @@ -112,14 +123,20 @@ public Builder setIndex(int index) {
return this;
}

@Nonnull
public Builder setInvisible(boolean invisible) {
this.invisible = invisible;
return this;
}

public RecordLayerColumn build() {
return new RecordLayerColumn(name, dataType, index);
return new RecordLayerColumn(name, dataType, index, invisible);
}
}

@Nonnull
public static RecordLayerColumn from(@Nonnull final DataType.StructType.Field field) {
return new RecordLayerColumn(field.getName(), field.getType(), field.getIndex());
return new RecordLayerColumn(field.getName(), field.getType(), field.getIndex(), field.isInvisible());
}

@Nonnull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ private Type.Record calculateRecordLayerType() {
private DataType.StructType calculateDataType() {
final var columnTypes = ImmutableList.<DataType.StructType.Field>builder();
for (final var column : columns) {
columnTypes.add(DataType.StructType.Field.from(column.getName(), column.getDataType(), column.getIndex()));
columnTypes.add(DataType.StructType.Field.from(column.getName(), column.getDataType(), column.getIndex(), column.isInvisible()));
}
/*
* TODO (yhatem): note this is not entirely correct. Currently we're not setting nullable
Expand Down Expand Up @@ -416,7 +416,7 @@ private static List<RecordLayerColumn> normalize(@Nonnull final List<RecordLayer
for (int i = 1; i <= columns.size(); i++) {
final var column = columns.get(i - 1);
if (column.getIndex() < 0) {
result.add(RecordLayerColumn.newBuilder().setName(column.getName()).setIndex(i).setDataType(column.getDataType()).build());
result.add(RecordLayerColumn.newBuilder().setName(column.getName()).setIndex(i).setDataType(column.getDataType()).setInvisible(column.isInvisible()).build());
} else {
result.add(column);
}
Expand Down
Loading
Loading