diff --git a/src/ibis_hotdata/backend.py b/src/ibis_hotdata/backend.py index c5ba6d5..89323e2 100644 --- a/src/ibis_hotdata/backend.py +++ b/src/ibis_hotdata/backend.py @@ -345,6 +345,13 @@ def _managed_table_synced( schema_name: str, table_name: str, ) -> bool: + """Return True only if the table exists and its last load has completed. + + A table whose ``synced`` flag is False is still being loaded; we treat + it as writable (returns False) so that an in-progress load can be + retried without requiring ``overwrite=True``. Tables not present in the + information schema also return False (not yet created). + """ for row in self._iterate_information_schema( {"connection_id": connection_id, "schema": schema_name, "table": table_name}, include_columns=False, @@ -613,9 +620,6 @@ def create_table( if temp: raise NotImplementedError("Hotdata does not support temporary tables.") - if obj is not None and schema is not None: - raise com.IbisInputError("create_table accepts only one of obj or schema") - data = self._local_table_to_parquet(obj, schema) connection_id, schema_name = self._table_location(database) if not overwrite and self._managed_table_synced(connection_id, schema_name, name): diff --git a/src/ibis_hotdata/managed.py b/src/ibis_hotdata/managed.py index 4f47d24..7357724 100644 --- a/src/ibis_hotdata/managed.py +++ b/src/ibis_hotdata/managed.py @@ -9,8 +9,6 @@ def build_managed_config(schema: str, tables: list[str]) -> dict[str, Any]: - if not tables: - return {} return { "schemas": [ { diff --git a/src/ibis_hotdata/types.py b/src/ibis_hotdata/types.py index c3a4bad..bc2f0b9 100644 --- a/src/ibis_hotdata/types.py +++ b/src/ibis_hotdata/types.py @@ -12,5 +12,5 @@ def dtype_from_hotdata_sql_type(sql_type: str | None, *, nullable: bool) -> dt.D return dt.String(nullable=nullable) try: return PostgresType.from_string(sql_type.strip(), nullable=nullable) - except Exception: + except Exception: # ibis/sqlglot raise a variety of parse errors; fall back to String return dt.String(nullable=nullable) diff --git a/tests/test_hotdata_backend.py b/tests/test_hotdata_backend.py index 6f4456e..197a8b7 100644 --- a/tests/test_hotdata_backend.py +++ b/tests/test_hotdata_backend.py @@ -499,6 +499,38 @@ def on_create(req: Request) -> Response: con.create_database("sales", schema="public", tables=["orders"]) +def test_create_database_no_tables_still_sends_schema(httpserver: HTTPServer, srv: str): + def on_create(req: Request) -> Response: + body = req.get_json() + assert body == { + "name": "empty_db", + "source_type": "managed", + "config": { + "schemas": [{"name": "analytics", "tables": []}], + }, + "skip_discovery": True, + } + return Response( + json.dumps( + { + "id": MANAGED_CONN, + "name": "empty_db", + "source_type": "managed", + "discovery_status": "skipped", + "tables_discovered": 0, + } + ), + status=201, + content_type="application/json", + ) + + httpserver.expect_request("/v1/connections", method="GET").respond_with_json({"connections": []}) + httpserver.expect_request("/v1/connections", method="POST").respond_with_handler(on_create) + + con = ibis.hotdata.connect(api_url=srv, token="tok", workspace_id="ws", verify_ssl=False) + con.create_database("empty_db", schema="analytics") + + def test_drop_table_deletes_managed_table(httpserver: HTTPServer, srv: str): httpserver.expect_request("/v1/connections").respond_with_json(managed_connections_response()) httpserver.expect_request( diff --git a/tests/test_hotdata_http.py b/tests/test_hotdata_http.py index 8402e44..b1932ca 100644 --- a/tests/test_hotdata_http.py +++ b/tests/test_hotdata_http.py @@ -124,6 +124,7 @@ def test_result_arrow_poll_handles_accepted_result(httpserver: HTTPServer): ) out = client.execute_query("select 1", poll_interval_s=0, poll_timeout_s=5) assert out["pa_table"].to_pydict() == {"n": [42]} + client.close() def test_async_query_run_failure(httpserver: HTTPServer):