diff --git a/ibis/backends/clickhouse/tests/test_datatypes.py b/ibis/backends/clickhouse/tests/test_datatypes.py index 3319128b9f72..6f78d4d50156 100644 --- a/ibis/backends/clickhouse/tests/test_datatypes.py +++ b/ibis/backends/clickhouse/tests/test_datatypes.py @@ -4,7 +4,6 @@ import hypothesis.strategies as st import pytest import sqlglot as sg -import sqlglot.expressions as sge from packaging.version import parse as vparse from pytest import param @@ -311,16 +310,5 @@ def test_type_roundtrip(ibis_type): def test_arrays_nullable(): - # if dtype.nullable and not (dtype.is_map() or dtype.is_array()): sge_type = ClickHouseType.from_ibis(dt.Array("float")) - typecode = sge.DataType.Type - - assert sge_type == sge.DataType( - this=typecode.ARRAY, - expressions=[ - sge.DataType( - this=typecode.NULLABLE, expressions=[sge.DataType(this=typecode.DOUBLE)] - ) - ], - nested=True, - ) + assert sge_type.sql("clickhouse") == "Array(Nullable(Float64))" diff --git a/ibis/backends/sql/compilers/datafusion.py b/ibis/backends/sql/compilers/datafusion.py index 155bf45d6584..6d1addae5193 100644 --- a/ibis/backends/sql/compilers/datafusion.py +++ b/ibis/backends/sql/compilers/datafusion.py @@ -505,5 +505,8 @@ def visit_GroupConcat(self, op, *, arg, sep, where, order_by): def visit_ArrayFlatten(self, op, *, arg): return self.if_(arg.is_(NULL), NULL, self.f.flatten(arg)) + def visit_RandomUUID(self, op, **kw): + return self.f.anon.uuid() + compiler = DataFusionCompiler() diff --git a/ibis/backends/sql/datatypes.py b/ibis/backends/sql/datatypes.py index 649e155ee705..d955a853d7b3 100644 --- a/ibis/backends/sql/datatypes.py +++ b/ibis/backends/sql/datatypes.py @@ -170,10 +170,13 @@ def to_ibis(cls, typ: sge.DataType, nullable: bool | None = None) -> dt.DataType ) typecode = typ.this + nullable = typ.args.get( + "nullable", nullable if nullable is not None else cls.default_nullable + ) if method := getattr(cls, f"_from_sqlglot_{typecode.name}", None): - dtype = method(*typ.expressions) + dtype = method(*typ.expressions, nullable=nullable) elif (known_typ := _from_sqlglot_types.get(typecode)) is not None: - dtype = known_typ(nullable=cls.default_nullable) + dtype = known_typ(nullable=nullable) else: dtype = dt.unknown @@ -196,6 +199,9 @@ def from_string(cls, text: str, nullable: bool | None = None) -> dt.DataType: if dtype := cls.unknown_type_strings.get(text.lower()): return dtype + if nullable is None: + nullable = cls.default_nullable + try: sgtype = sg.parse_one(text, into=sge.DataType, read=cls.dialect) except sg.errors.ParseError: @@ -209,65 +215,83 @@ def to_string(cls, dtype: dt.DataType) -> str: return cls.from_ibis(dtype).sql(dialect=cls.dialect) @classmethod - def _from_sqlglot_ARRAY(cls, value_type: sge.DataType) -> dt.Array: - return dt.Array(cls.to_ibis(value_type), nullable=cls.default_nullable) + def _from_sqlglot_ARRAY( + cls, value_type: sge.DataType, nullable: bool | None = None + ) -> dt.Array: + return dt.Array(cls.to_ibis(value_type), nullable=nullable) @classmethod def _from_sqlglot_MAP( - cls, key_type: sge.DataType, value_type: sge.DataType + cls, + key_type: sge.DataType, + value_type: sge.DataType, + nullable: bool | None = None, ) -> dt.Map: - return dt.Map( - cls.to_ibis(key_type), - cls.to_ibis(value_type), - nullable=cls.default_nullable, - ) + return dt.Map(cls.to_ibis(key_type), cls.to_ibis(value_type), nullable=nullable) @classmethod - def _from_sqlglot_STRUCT(cls, *fields: sge.ColumnDef) -> dt.Struct: + def _from_sqlglot_STRUCT( + cls, *fields: sge.ColumnDef, nullable: bool | None = None + ) -> dt.Struct: types = {} for i, field in enumerate(fields): if isinstance(field, sge.ColumnDef): - types[field.name] = cls.to_ibis(field.args["kind"]) + name = field.name + sgtype = field.args["kind"] else: - types[f"f{i:d}"] = cls.from_string(str(field)) - return dt.Struct(types, nullable=cls.default_nullable) + # handle unnamed fields (e.g., ClickHouse's Tuple type) + assert isinstance(field, sge.DataType), type(field) + name = f"f{i:d}" + sgtype = field + + types[name] = cls.to_ibis(sgtype) + return dt.Struct(types, nullable=nullable) @classmethod - def _from_sqlglot_TIMESTAMP(cls, scale=None) -> dt.Timestamp: + def _from_sqlglot_TIMESTAMP( + cls, scale=None, nullable: bool | None = None + ) -> dt.Timestamp: return dt.Timestamp( scale=cls.default_temporal_scale if scale is None else int(scale.this.this), - nullable=cls.default_nullable, + nullable=nullable, ) @classmethod - def _from_sqlglot_TIMESTAMPTZ(cls, scale=None) -> dt.Timestamp: + def _from_sqlglot_TIMESTAMPTZ( + cls, scale=None, nullable: bool | None = None + ) -> dt.Timestamp: return dt.Timestamp( timezone="UTC", scale=cls.default_temporal_scale if scale is None else int(scale.this.this), - nullable=cls.default_nullable, + nullable=nullable, ) @classmethod - def _from_sqlglot_TIMESTAMPLTZ(cls, scale=None) -> dt.Timestamp: + def _from_sqlglot_TIMESTAMPLTZ( + cls, scale=None, nullable: bool | None = None + ) -> dt.Timestamp: return dt.Timestamp( timezone="UTC", scale=cls.default_temporal_scale if scale is None else int(scale.this.this), - nullable=cls.default_nullable, + nullable=nullable, ) @classmethod - def _from_sqlglot_TIMESTAMPNTZ(cls, scale=None) -> dt.Timestamp: + def _from_sqlglot_TIMESTAMPNTZ( + cls, scale=None, nullable: bool | None = None + ) -> dt.Timestamp: return dt.Timestamp( timezone=None, scale=cls.default_temporal_scale if scale is None else int(scale.this.this), - nullable=cls.default_nullable, + nullable=nullable, ) @classmethod def _from_sqlglot_INTERVAL( - cls, precision_or_span: sge.IntervalSpan | None = None + cls, + precision_or_span: sge.IntervalSpan | None = None, + nullable: bool | None = None, ) -> dt.Interval: - nullable = cls.default_nullable if precision_or_span is None: precision_or_span = cls.default_interval_precision @@ -291,6 +315,7 @@ def _from_sqlglot_DECIMAL( cls, precision: sge.DataTypeParam | None = None, scale: sge.DataTypeParam | None = None, + nullable: bool | None = None, ) -> dt.Decimal: if precision is None: precision = cls.default_decimal_precision @@ -302,11 +327,14 @@ def _from_sqlglot_DECIMAL( else: scale = int(scale.this.this) - return dt.Decimal(precision, scale, nullable=cls.default_nullable) + return dt.Decimal(precision, scale, nullable=nullable) @classmethod def _from_sqlglot_GEOMETRY( - cls, arg: sge.DataTypeParam | None = None, srid: sge.DataTypeParam | None = None + cls, + arg: sge.DataTypeParam | None = None, + srid: sge.DataTypeParam | None = None, + nullable: bool | None = None, ) -> sge.DataType: if arg is not None: typeclass = _geotypes[arg.this.this] @@ -314,11 +342,14 @@ def _from_sqlglot_GEOMETRY( typeclass = dt.GeoSpatial if srid is not None: srid = int(srid.this.this) - return typeclass(geotype="geometry", nullable=cls.default_nullable, srid=srid) + return typeclass(geotype="geometry", nullable=nullable, srid=srid) @classmethod def _from_sqlglot_GEOGRAPHY( - cls, arg: sge.DataTypeParam | None = None, srid: sge.DataTypeParam | None = None + cls, + arg: sge.DataTypeParam | None = None, + srid: sge.DataTypeParam | None = None, + nullable: bool | None = None, ) -> sge.DataType: if arg is not None: typeclass = _geotypes[arg.this.this] @@ -326,7 +357,7 @@ def _from_sqlglot_GEOGRAPHY( typeclass = dt.GeoSpatial if srid is not None: srid = int(srid.this.this) - return typeclass(geotype="geography", nullable=cls.default_nullable, srid=srid) + return typeclass(geotype="geography", nullable=nullable, srid=srid) @classmethod def _from_ibis_JSON(cls, dtype: dt.JSON) -> sge.DataType: @@ -473,7 +504,9 @@ def from_string(cls, text: str, nullable: bool | None = None) -> dt.DataType: if text.lower().startswith("vector"): text = "vector" - return super().from_string(text, nullable=nullable) + return super().from_string( + text, nullable=nullable if nullable is not None else cls.default_nullable + ) class RisingWaveType(PostgresType): @@ -517,19 +550,23 @@ class MySQLType(SqlglotType): ) @classmethod - def _from_sqlglot_BIT(cls, nbits: sge.DataTypeParam) -> dt.Integer: + def _from_sqlglot_BIT( + cls, nbits: sge.DataTypeParam, nullable: bool | None = None + ) -> dt.Integer: nbits = int(nbits.this.this) if nbits > 32: - return dt.Int64(nullable=cls.default_nullable) + return dt.Int64(nullable=nullable) elif nbits > 16: - return dt.Int32(nullable=cls.default_nullable) + return dt.Int32(nullable=nullable) elif nbits > 8: - return dt.Int16(nullable=cls.default_nullable) + return dt.Int16(nullable=nullable) else: - return dt.Int8(nullable=cls.default_nullable) + return dt.Int8(nullable=nullable) @classmethod - def _from_sqlglot_DATETIME(cls, scale=None) -> dt.Timestamp: + def _from_sqlglot_DATETIME( + cls, scale=None, nullable: bool | None = None + ) -> dt.Timestamp: if scale is not None: scale = int(scale.this.this) return dt.Timestamp( @@ -540,12 +577,12 @@ def _from_sqlglot_DATETIME(cls, scale=None) -> dt.Timestamp: # https://dev.mysql.com/doc/refman/8.4/en/fractional-seconds.html # for details scale=scale or None, - nullable=cls.default_nullable, + nullable=nullable, ) @classmethod - def _from_sqlglot_TIMESTAMP(cls) -> dt.Timestamp: - return dt.Timestamp(timezone="UTC", nullable=cls.default_nullable) + def _from_sqlglot_TIMESTAMP(cls, nullable: bool | None = None) -> dt.Timestamp: + return dt.Timestamp(timezone="UTC", nullable=nullable) @classmethod def _from_ibis_String(cls, dtype: dt.String) -> sge.DataType: @@ -561,24 +598,24 @@ class DuckDBType(SqlglotType): unknown_type_strings = FrozenDict({"wkb_blob": dt.binary}) @classmethod - def _from_sqlglot_TIMESTAMP(cls) -> dt.Timestamp: - return dt.Timestamp(scale=6, nullable=cls.default_nullable) + def _from_sqlglot_TIMESTAMP(cls, nullable: bool | None = None) -> dt.Timestamp: + return dt.Timestamp(scale=6, nullable=nullable) @classmethod - def _from_sqlglot_TIMESTAMPTZ(cls) -> dt.Timestamp: - return dt.Timestamp(scale=6, timezone="UTC", nullable=cls.default_nullable) + def _from_sqlglot_TIMESTAMPTZ(cls, nullable: bool | None = None) -> dt.Timestamp: + return dt.Timestamp(scale=6, timezone="UTC", nullable=nullable) @classmethod - def _from_sqlglot_TIMESTAMP_S(cls) -> dt.Timestamp: - return dt.Timestamp(scale=0, nullable=cls.default_nullable) + def _from_sqlglot_TIMESTAMP_S(cls, nullable: bool | None = None) -> dt.Timestamp: + return dt.Timestamp(scale=0, nullable=nullable) @classmethod - def _from_sqlglot_TIMESTAMP_MS(cls) -> dt.Timestamp: - return dt.Timestamp(scale=3, nullable=cls.default_nullable) + def _from_sqlglot_TIMESTAMP_MS(cls, nullable: bool | None = None) -> dt.Timestamp: + return dt.Timestamp(scale=3, nullable=nullable) @classmethod - def _from_sqlglot_TIMESTAMP_NS(cls) -> dt.Timestamp: - return dt.Timestamp(scale=9, nullable=cls.default_nullable) + def _from_sqlglot_TIMESTAMP_NS(cls, nullable: bool | None = None) -> dt.Timestamp: + return dt.Timestamp(scale=9, nullable=nullable) @classmethod def _from_ibis_GeoSpatial(cls, dtype: dt.GeoSpatial): @@ -628,8 +665,8 @@ def _from_ibis_Interval(cls, dtype: dt.Interval) -> sge.DataType: ) @classmethod - def _from_sqlglot_UBIGINT(cls): - return dt.Decimal(precision=19, scale=0, nullable=cls.default_nullable) + def _from_sqlglot_UBIGINT(cls, nullable: bool | None = None): + return dt.Decimal(precision=19, scale=0, nullable=nullable) @classmethod def _from_ibis_UInt64(cls, dtype): @@ -642,24 +679,24 @@ def _from_ibis_UInt64(cls, dtype): ) @classmethod - def _from_sqlglot_UINT(cls): - return dt.Int64(nullable=cls.default_nullable) + def _from_sqlglot_UINT(cls, nullable: bool | None = None): + return dt.Int64(nullable=nullable) @classmethod def _from_ibis_UInt32(cls, dtype): return sge.DataType(this=typecode.BIGINT) @classmethod - def _from_sqlglot_USMALLINT(cls): - return dt.Int32(nullable=cls.default_nullable) + def _from_sqlglot_USMALLINT(cls, nullable: bool | None = None): + return dt.Int32(nullable=nullable) @classmethod def _from_ibis_UInt16(cls, dtype): return sge.DataType(this=typecode.INT) @classmethod - def _from_sqlglot_UTINYINT(cls): - return dt.Int16(nullable=cls.default_nullable) + def _from_sqlglot_UTINYINT(cls, nullable: bool | None = None): + return dt.Int16(nullable=nullable) @classmethod def _from_ibis_UInt8(cls, dtype): @@ -683,13 +720,15 @@ class OracleType(SqlglotType): unknown_type_strings = FrozenDict({"raw": dt.binary}) @classmethod - def _from_sqlglot_FLOAT(cls) -> dt.Float64: - return dt.Float64(nullable=cls.default_nullable) + def _from_sqlglot_FLOAT(cls, nullable: bool | None = None) -> dt.Float64: + return dt.Float64(nullable=nullable) @classmethod - def _from_sqlglot_DECIMAL(cls, precision=None, scale=None) -> dt.Decimal: + def _from_sqlglot_DECIMAL( + cls, precision=None, scale=None, nullable: bool | None = None + ) -> dt.Decimal: if scale is None or int(scale.this.this) == 0: - return dt.Int64(nullable=cls.default_nullable) + return dt.Int64(nullable=nullable) else: return super()._from_sqlglot_DECIMAL(precision, scale) @@ -708,20 +747,24 @@ class SnowflakeType(SqlglotType): default_temporal_scale = 9 @classmethod - def _from_sqlglot_FLOAT(cls) -> dt.Float64: - return dt.Float64(nullable=cls.default_nullable) + def _from_sqlglot_FLOAT(cls, nullable: bool | None = None) -> dt.Float64: + return dt.Float64(nullable=nullable) @classmethod - def _from_sqlglot_DECIMAL(cls, precision=None, scale=None) -> dt.Decimal: + def _from_sqlglot_DECIMAL( + cls, precision=None, scale=None, nullable: bool | None = None + ) -> dt.Decimal: if scale is None or int(scale.this.this) == 0: - return dt.Int64(nullable=cls.default_nullable) + return dt.Int64(nullable=nullable) else: - return super()._from_sqlglot_DECIMAL(precision, scale) + return super()._from_sqlglot_DECIMAL(precision, scale, nullable=nullable) @classmethod - def _from_sqlglot_ARRAY(cls, value_type=None) -> dt.Array: + def _from_sqlglot_ARRAY( + cls, value_type=None, nullable: bool | None = None + ) -> dt.Array: assert value_type is None - return dt.Array(dt.json, nullable=cls.default_nullable) + return dt.Array(dt.json, nullable=nullable) @classmethod def _from_ibis_JSON(cls, dtype: dt.JSON) -> sge.DataType: @@ -744,12 +787,12 @@ class SQLiteType(SqlglotType): dialect = "sqlite" @classmethod - def _from_sqlglot_INT(cls) -> dt.Int64: - return dt.Int64(nullable=cls.default_nullable) + def _from_sqlglot_INT(cls, nullable: bool | None = None) -> dt.Int64: + return dt.Int64(nullable=nullable) @classmethod - def _from_sqlglot_FLOAT(cls) -> dt.Float64: - return dt.Float64(nullable=cls.default_nullable) + def _from_sqlglot_FLOAT(cls, nullable: bool | None = None) -> dt.Float64: + return dt.Float64(nullable=nullable) @classmethod def _from_ibis_Array(cls, dtype: dt.Array) -> NoReturn: @@ -797,40 +840,39 @@ class BigQueryType(SqlglotType): default_decimal_scale = 9 @classmethod - def _from_sqlglot_NUMERIC(cls) -> dt.Decimal: + def _from_sqlglot_NUMERIC(cls, nullable: bool | None = None) -> dt.Decimal: return dt.Decimal( - cls.default_decimal_precision, - cls.default_decimal_scale, - nullable=cls.default_nullable, + cls.default_decimal_precision, cls.default_decimal_scale, nullable=nullable ) @classmethod - def _from_sqlglot_BIGNUMERIC(cls) -> dt.Decimal: - return dt.Decimal(76, 38, nullable=cls.default_nullable) + def _from_sqlglot_BIGNUMERIC(cls, nullable: bool | None = None) -> dt.Decimal: + return dt.Decimal(76, 38, nullable=nullable) @classmethod - def _from_sqlglot_DATETIME(cls) -> dt.Timestamp: - return dt.Timestamp(timezone=None, nullable=cls.default_nullable) + def _from_sqlglot_DATETIME(cls, nullable: bool | None = None) -> dt.Timestamp: + return dt.Timestamp(timezone=None, nullable=nullable) @classmethod - def _from_sqlglot_TIMESTAMP(cls) -> dt.Timestamp: - return dt.Timestamp(timezone=None, nullable=cls.default_nullable) + def _from_sqlglot_TIMESTAMP(cls, nullable: bool | None = None) -> dt.Timestamp: + return dt.Timestamp(timezone=None, nullable=nullable) @classmethod - def _from_sqlglot_TIMESTAMPTZ(cls) -> dt.Timestamp: - return dt.Timestamp(timezone="UTC", nullable=cls.default_nullable) + def _from_sqlglot_TIMESTAMPTZ(cls, nullable: bool | None = None) -> dt.Timestamp: + return dt.Timestamp(timezone="UTC", nullable=nullable) @classmethod def _from_sqlglot_GEOGRAPHY( - cls, arg: sge.DataTypeParam | None = None, srid: sge.DataTypeParam | None = None + cls, + arg: sge.DataTypeParam | None = None, + srid: sge.DataTypeParam | None = None, + nullable: bool | None = None, ) -> dt.GeoSpatial: - return dt.GeoSpatial( - geotype="geography", srid=4326, nullable=cls.default_nullable - ) + return dt.GeoSpatial(geotype="geography", srid=4326, nullable=nullable) @classmethod - def _from_sqlglot_TINYINT(cls) -> dt.Int64: - return dt.Int64(nullable=cls.default_nullable) + def _from_sqlglot_TINYINT(cls, nullable: bool | None = None) -> dt.Int64: + return dt.Int64(nullable=nullable) _from_sqlglot_UINT = _from_sqlglot_USMALLINT = _from_sqlglot_UTINYINT = ( _from_sqlglot_INT @@ -843,8 +885,8 @@ def _from_sqlglot_UBIGINT(cls) -> NoReturn: ) @classmethod - def _from_sqlglot_FLOAT(cls) -> dt.Float64: - return dt.Float64(nullable=cls.default_nullable) + def _from_sqlglot_FLOAT(cls, nullable: bool | None = None) -> dt.Float64: + return dt.Float64(nullable=nullable) @classmethod def _from_sqlglot_MAP(cls) -> NoReturn: @@ -931,6 +973,7 @@ def _from_sqlglot_DECIMAL( cls, precision: sge.DataTypeParam | None = None, scale: sge.DataTypeParam | None = None, + nullable: bool | None = None, ) -> dt.Decimal: if precision is None: precision = cls.default_decimal_precision @@ -944,18 +987,18 @@ def _from_sqlglot_DECIMAL( if not scale: if 0 < precision <= 3: - return dt.Int8(nullable=cls.default_nullable) + return dt.Int8(nullable=nullable) elif 3 < precision <= 9: - return dt.Int16(nullable=cls.default_nullable) + return dt.Int16(nullable=nullable) elif 9 < precision <= 18: - return dt.Int32(nullable=cls.default_nullable) + return dt.Int32(nullable=nullable) elif 18 < precision <= 36: - return dt.Int64(nullable=cls.default_nullable) + return dt.Int64(nullable=nullable) else: raise com.UnsupportedBackendType( "Decimal precision is too large; Exasol supports precision up to 36." ) - return dt.Decimal(precision, scale, nullable=cls.default_nullable) + return dt.Decimal(precision, scale, nullable=nullable) @classmethod def _from_ibis_Array(cls, dtype: dt.Array) -> NoReturn: @@ -993,17 +1036,17 @@ class MSSQLType(SqlglotType): unknown_type_strings = FrozenDict({"hierarchyid": dt.string}) @classmethod - def _from_sqlglot_BIT(cls): - return dt.Boolean(nullable=cls.default_nullable) + def _from_sqlglot_BIT(cls, nullable: bool | None = None): + return dt.Boolean(nullable=nullable) @classmethod - def _from_sqlglot_IMAGE(cls): - return dt.Binary(nullable=cls.default_nullable) + def _from_sqlglot_IMAGE(cls, nullable: bool | None = None): + return dt.Binary(nullable=nullable) @classmethod - def _from_sqlglot_DATETIME(cls, n=None): + def _from_sqlglot_DATETIME(cls, n=None, nullable: bool | None = None): return dt.Timestamp( - scale=n if n is None else int(n.this.this), nullable=cls.default_nullable + scale=n if n is None else int(n.this.this), nullable=nullable ) @classmethod @@ -1062,30 +1105,36 @@ class ClickHouseType(SqlglotType): def from_ibis(cls, dtype: dt.DataType) -> sge.DataType: typ = super().from_ibis(dtype) - if typ.this == typecode.NULLABLE: + if typ.args.get("nullable") is True: return typ - # nested types cannot be nullable in clickhouse - typ.args["nullable"] = False - if dtype.nullable and not ( + typ.args["nullable"] = dtype.nullable and not ( + # nested types cannot be nullable in clickhouse dtype.is_map() or dtype.is_array() or dtype.is_struct() - ): - return sge.DataType(this=typecode.NULLABLE, expressions=[typ]) - else: - return typ + ) + return typ @classmethod - def _from_sqlglot_NULLABLE(cls, inner_type: sge.DataType) -> dt.DataType: + def _from_sqlglot_NULLABLE( + cls, + inner_type: sge.DataType, + # nullable is ignored when explicitly wrapped in ClickHouse's Nullable + # type modifier + # + # NULLABLE was removed in sqlglot 25.11, but this remains for backwards + # compatibility in Ibis + nullable: bool | None = None, + ) -> dt.DataType: return cls.to_ibis(inner_type, nullable=True) @classmethod def _from_sqlglot_DATETIME( - cls, timezone: sge.DataTypeParam | None = None + cls, timezone: sge.DataTypeParam | None = None, nullable: bool | None = None ) -> dt.Timestamp: return dt.Timestamp( scale=0, timezone=None if timezone is None else timezone.this.this, - nullable=cls.default_nullable, + nullable=nullable, ) @classmethod @@ -1093,26 +1142,29 @@ def _from_sqlglot_DATETIME64( cls, scale: sge.DataTypeSize | None = None, timezone: sge.Literal | None = None, + nullable: bool | None = None, ) -> dt.Timestamp: return dt.Timestamp( timezone=None if timezone is None else timezone.this.this, scale=int(scale.this.this), - nullable=cls.default_nullable, + nullable=nullable, ) @classmethod - def _from_sqlglot_LOWCARDINALITY(cls, inner_type: sge.DataType) -> dt.DataType: - return cls.to_ibis(inner_type) + def _from_sqlglot_LOWCARDINALITY( + cls, inner_type: sge.DataType, nullable: bool | None = None + ) -> dt.DataType: + return cls.to_ibis(inner_type, nullable=nullable) @classmethod - def _from_sqlglot_NESTED(cls, *fields: sge.DataType) -> dt.Struct: + def _from_sqlglot_NESTED( + cls, *fields: sge.DataType, nullable: bool | None = None + ) -> dt.Struct: fields = { - field.name: dt.Array( - cls.to_ibis(field.args["kind"]), nullable=cls.default_nullable - ) + field.name: dt.Array(cls.to_ibis(field.args["kind"]), nullable=nullable) for field in fields } - return dt.Struct(fields, nullable=cls.default_nullable) + return dt.Struct(fields, nullable=nullable) @classmethod def _from_ibis_Timestamp(cls, dtype: dt.Timestamp) -> sge.DataType: diff --git a/ibis/backends/tests/snapshots/test_sql/test_selects_with_impure_operations_not_merged/bigquery-uuid/out.sql b/ibis/backends/tests/snapshots/test_sql/test_selects_with_impure_operations_not_merged/bigquery-uuid/out.sql index 2e9d56892510..ffb8ba5d189b 100644 --- a/ibis/backends/tests/snapshots/test_sql/test_selects_with_impure_operations_not_merged/bigquery-uuid/out.sql +++ b/ibis/backends/tests/snapshots/test_sql/test_selects_with_impure_operations_not_merged/bigquery-uuid/out.sql @@ -6,7 +6,7 @@ SELECT FROM ( SELECT `t0`.`x`, - generate_uuid() AS `y`, - generate_uuid() AS `z` + GENERATE_UUID() AS `y`, + GENERATE_UUID() AS `z` FROM `t` AS `t0` ) AS `t1` \ No newline at end of file diff --git a/ibis/backends/tests/test_client.py b/ibis/backends/tests/test_client.py index 9ed5078b78cb..2d18a65fcf09 100644 --- a/ibis/backends/tests/test_client.py +++ b/ibis/backends/tests/test_client.py @@ -1003,15 +1003,13 @@ def test_self_join_memory_table(backend, con, monkeypatch): ) def test_create_table_in_memory(con, obj, table_name, monkeypatch): monkeypatch.setattr(ibis.options, "default_backend", con) - obj = obj() - t = con.create_table(table_name, obj) + t = con.create_table(table_name, obj()) - result = pa.table({"a": ["a"], "b": [1]}) - assert table_name in con.list_tables() - - assert result.equals(t.to_pyarrow()) - - con.drop_table(table_name, force=True) + try: + assert table_name in con.list_tables() + assert pa.table({"a": ["a"], "b": [1]}).equals(t.to_pyarrow()) + finally: + con.drop_table(table_name, force=True) def test_default_backend_option(con, monkeypatch): diff --git a/poetry.lock b/poetry.lock index 30a2ce1a39ec..c6db8dc8921b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -6843,13 +6843,13 @@ sqlcipher = ["sqlcipher3_binary"] [[package]] name = "sqlglot" -version = "25.20.1" +version = "25.21.3" description = "An easily customizable SQL parser and transpiler" optional = false python-versions = ">=3.7" files = [ - {file = "sqlglot-25.20.1-py3-none-any.whl", hash = "sha256:ea8c957ed22cc825d7714c46e165b66da33921492124f4d6b7cc742a1a960ec4"}, - {file = "sqlglot-25.20.1.tar.gz", hash = "sha256:495afc1aa26dabedfe2faf9c655779eaf6e2401686a20920b742786d26a26cb0"}, + {file = "sqlglot-25.21.3-py3-none-any.whl", hash = "sha256:dc63b429b80a69f2240ef892f776830883667fc9d978984ab98e7ce07edb7057"}, + {file = "sqlglot-25.21.3.tar.gz", hash = "sha256:273a447f71434ab2f9a36b81a6327706369735a0756a61cd576ac6896a5086a4"}, ] [package.extras] @@ -7850,4 +7850,4 @@ visualization = ["graphviz"] [metadata] lock-version = "2.0" python-versions = "^3.10" -content-hash = "ee02b6724647e52fe93cbb3259dba3956a3e7349d7b8c5c8a48bf254b8f9b9ab" +content-hash = "c3905989eab1b3ab585b029a685e38ac6fa10febb022f15f3b1aea5e449e23fd" diff --git a/pyproject.toml b/pyproject.toml index cab521be4841..c70a7682dfed 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,7 @@ atpublic = ">=2.3,<6" parsy = ">=2,<3" python-dateutil = ">=2.8.2,<3" pytz = ">=2022.7" -sqlglot = ">=23.4,<25.21" +sqlglot = ">=23.4,<25.22" toolz = ">=0.11,<1" typing-extensions = ">=4.3.0,<5" numpy = { version = ">=1.23.2,<3", optional = true } diff --git a/requirements-dev.txt b/requirements-dev.txt index de5cd8084598..49007e64d70f 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -254,7 +254,7 @@ sortedcontainers==2.4.0 ; python_version >= "3.10" and python_version < "4.0" soupsieve==2.6 ; python_version >= "3.10" and python_version < "3.13" sphobjinv==2.3.1.1 ; python_version >= "3.10" and python_version < "3.13" sqlalchemy==2.0.34 ; python_version >= "3.10" and python_version < "3.13" -sqlglot==25.20.1 ; python_version >= "3.10" and python_version < "4.0" +sqlglot==25.21.3 ; python_version >= "3.10" and python_version < "4.0" stack-data==0.6.3 ; python_version >= "3.10" and python_version < "4.0" statsmodels==0.14.2 ; python_version >= "3.10" and python_version < "3.13" tabulate==0.9.0 ; python_version >= "3.10" and python_version < "3.13"