Skip to content

Commit f76a452

Browse files
gordthompsonrafiss
authored andcommitted
Add include_hidden option to get_columns()
Fixes: #173
1 parent 163b7eb commit f76a452

File tree

3 files changed

+121
-32
lines changed

3 files changed

+121
-32
lines changed

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Version 1.4.4
22
Unreleased
33

4+
- Added `include_hidden` option to `get_columns()` to enable reflection of columns like "rowid". (#173)
5+
46
# Version 1.4.3
57
Released Devember 10, 2021
68

sqlalchemy_cockroachdb/base.py

Lines changed: 26 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -154,12 +154,10 @@ def initialize(self, connection):
154154
sqlalchemy_version_string = matches[0][0]
155155
telemetry_query = "SELECT crdb_internal.increment_feature_counter(:val)"
156156
connection.execute(
157-
text(telemetry_query),
158-
dict(val=f'sqlalchemy-cockroachdb {dialect_version}')
157+
text(telemetry_query), dict(val=f"sqlalchemy-cockroachdb {dialect_version}")
159158
)
160159
connection.execute(
161-
text(telemetry_query),
162-
dict(val=f'sqlalchemy {sqlalchemy_version_string}')
160+
text(telemetry_query), dict(val=f"sqlalchemy {sqlalchemy_version_string}")
163161
)
164162

165163
def _get_server_version_info(self, conn):
@@ -191,39 +189,40 @@ def has_table(self, conn, table, schema=None):
191189
# The upstream implementations of the reflection functions below depend on
192190
# correlated subqueries which are not yet supported.
193191
def get_columns(self, conn, table_name, schema=None, **kw):
192+
_include_hidden = kw.get("include_hidden", False)
194193
if not self._is_v2plus:
195194
# v1.1.
196195
# Bad: the table name is not properly escaped.
197196
# Oh well. Hoping 1.1 won't be around for long.
198197
rows = conn.execute(
199-
text(
200-
f'SHOW COLUMNS FROM "{schema or self.default_schema_name}"."{table_name}"'
201-
)
198+
text(f'SHOW COLUMNS FROM "{schema or self.default_schema_name}"."{table_name}"')
202199
)
203200
elif not self._is_v191plus:
204201
# v2.x does not have is_generated or generation_expression
202+
sql = (
203+
"SELECT column_name, data_type, is_nullable::bool, column_default, "
204+
"numeric_precision, numeric_scale, character_maximum_length, "
205+
"NULL AS is_generated, NULL AS generation_expression, is_hidden::bool "
206+
"FROM information_schema.columns "
207+
"WHERE table_schema = :table_schema AND table_name = :table_name "
208+
)
209+
sql += "" if _include_hidden else "AND NOT is_hidden::bool"
205210
rows = conn.execute(
206-
text(
207-
"SELECT column_name, data_type, is_nullable::bool, column_default, "
208-
"numeric_precision, numeric_scale, character_maximum_length, "
209-
"NULL AS is_generated, NULL AS generation_expression "
210-
"FROM information_schema.columns "
211-
"WHERE table_schema = :table_schema AND table_name = :table_name "
212-
" AND NOT is_hidden::bool"
213-
),
211+
text(sql),
214212
{"table_schema": schema or self.default_schema_name, "table_name": table_name},
215213
)
216214
else:
217215
# v19.1 or later. Information schema columns are all usable.
216+
sql = (
217+
"SELECT column_name, data_type, is_nullable::bool, column_default, "
218+
"numeric_precision, numeric_scale, character_maximum_length, "
219+
"is_generated::bool, generation_expression, is_hidden::bool "
220+
"FROM information_schema.columns "
221+
"WHERE table_schema = :table_schema AND table_name = :table_name "
222+
)
223+
sql += "" if _include_hidden else "AND NOT is_hidden::bool"
218224
rows = conn.execute(
219-
text(
220-
"SELECT column_name, data_type, is_nullable::bool, column_default, "
221-
"numeric_precision, numeric_scale, character_maximum_length, "
222-
"is_generated::bool, generation_expression "
223-
"FROM information_schema.columns "
224-
"WHERE table_schema = :table_schema AND table_name = :table_name "
225-
" AND NOT is_hidden::bool"
226-
),
225+
text(sql),
227226
{"table_schema": schema or self.default_schema_name, "table_name": table_name},
228227
)
229228

@@ -292,6 +291,7 @@ def get_columns(self, conn, table_name, schema=None, **kw):
292291
nullable=nullable,
293292
default=default,
294293
autoincrement=autoincrement,
294+
is_hidden=row.is_hidden,
295295
)
296296
if computed is not None:
297297
column_info["computed"] = computed
@@ -355,9 +355,7 @@ def get_foreign_keys_v1(self, conn, table_name, schema=None, **kw):
355355
FK_REGEX = re.compile(r"(?P<referred_table>.+)?\.\[(?P<referred_columns>.+)?]")
356356

357357
for row in conn.execute(
358-
text(
359-
f'SHOW CONSTRAINTS FROM "{schema or self.default_schema_name}"."{table_name}"'
360-
)
358+
text(f'SHOW CONSTRAINTS FROM "{schema or self.default_schema_name}"."{table_name}"')
361359
):
362360
if row.Type.startswith("FOREIGN KEY"):
363361
m = re.search(FK_REGEX, row.Details)
@@ -511,9 +509,7 @@ def get_pk_constraint(self, conn, table_name, schema=None, **kw):
511509

512510
def get_unique_constraints(self, conn, table_name, schema=None, **kw):
513511
if self._is_v21plus:
514-
return super().get_unique_constraints(
515-
conn, table_name, schema, **kw
516-
)
512+
return super().get_unique_constraints(conn, table_name, schema, **kw)
517513

518514
# v2.0 does not know about enough SQL to understand the query done by
519515
# the upstream dialect. So run a dumbed down version instead.
@@ -531,9 +527,7 @@ def get_unique_constraints(self, conn, table_name, schema=None, **kw):
531527

532528
def get_check_constraints(self, conn, table_name, schema=None, **kw):
533529
if self._is_v21plus:
534-
return super().get_check_constraints(
535-
conn, table_name, schema, **kw
536-
)
530+
return super().get_check_constraints(conn, table_name, schema, **kw)
537531
# TODO(bdarnell): The postgres dialect implementation depends on
538532
# pg_table_is_visible, which is supported in cockroachdb 1.1
539533
# but not in 1.0. Figure out a versioning strategy.

test/test_column_reflect.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
from sqlalchemy import MetaData, Table, Column, Integer, String, testing, inspect
2+
from sqlalchemy.testing import fixtures, eq_
3+
4+
meta = MetaData()
5+
6+
with_pk = Table(
7+
"with_pk",
8+
meta,
9+
Column("id", Integer, primary_key=True),
10+
Column("txt", String),
11+
)
12+
13+
without_pk = Table(
14+
"without_pk",
15+
meta,
16+
Column("txt", String),
17+
)
18+
19+
20+
class ReflectHiddenColumnsTest(fixtures.TestBase):
21+
__requires__ = ("sync_driver",)
22+
23+
def teardown_method(self, method):
24+
meta.drop_all(testing.db)
25+
26+
def setup_method(self):
27+
meta.create_all(testing.db)
28+
29+
def _get_col_info(self, table_name, include_hidden=False):
30+
insp = inspect(testing.db)
31+
col_info = insp.get_columns(table_name, include_hidden=include_hidden)
32+
for row in col_info:
33+
row["type"] = str(row["type"])
34+
return col_info
35+
36+
def test_reflect_hidden_columns(self):
37+
eq_(
38+
self._get_col_info("with_pk"),
39+
[
40+
{
41+
"name": "id",
42+
"type": "INTEGER",
43+
"nullable": False,
44+
"default": "unique_rowid()",
45+
"autoincrement": True,
46+
"is_hidden": False,
47+
},
48+
{
49+
"name": "txt",
50+
"type": "VARCHAR",
51+
"nullable": True,
52+
"default": None,
53+
"autoincrement": False,
54+
"is_hidden": False,
55+
},
56+
],
57+
)
58+
59+
eq_(
60+
self._get_col_info("without_pk"), # include_hidden=False
61+
[
62+
{
63+
"name": "txt",
64+
"type": "VARCHAR",
65+
"nullable": True,
66+
"default": None,
67+
"autoincrement": False,
68+
"is_hidden": False,
69+
},
70+
],
71+
)
72+
73+
eq_(
74+
self._get_col_info("without_pk", include_hidden=True),
75+
[
76+
{
77+
"name": "txt",
78+
"type": "VARCHAR",
79+
"nullable": True,
80+
"default": None,
81+
"autoincrement": False,
82+
"is_hidden": False,
83+
},
84+
{
85+
"name": "rowid",
86+
"type": "INTEGER",
87+
"nullable": False,
88+
"default": "unique_rowid()",
89+
"autoincrement": True,
90+
"is_hidden": True,
91+
},
92+
],
93+
)

0 commit comments

Comments
 (0)