Skip to content

Commit 0a64272

Browse files
authored
Merge pull request #36 from codeforkjeff/improve-type-handling
Issue #30: Improve type handling
2 parents 772667f + 1688365 commit 0a64272

File tree

6 files changed

+63
-123
lines changed

6 files changed

+63
-123
lines changed

Dockerfile

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,15 @@ RUN wget -q https://github.com/nalgeon/sqlean/releases/download/0.15.2/text.so
1616

1717
WORKDIR /opt/dbt-sqlite/src
1818

19-
COPY . .
19+
COPY setup.py .
20+
COPY dbt ./dbt
2021

2122
RUN pip install .
2223

24+
COPY run_tests.sh .
25+
COPY pytest.ini .
26+
COPY tests ./tests
27+
2328
ENV TESTDATA=/opt/dbt-sqlite/testdata
2429

2530
RUN mkdir $TESTDATA

dbt/adapters/sqlite/impl.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,9 @@ class SQLiteAdapter(SQLAdapter):
2424
def date_function(cls):
2525
return 'date()'
2626

27-
# sqlite reports the exact string (including case) used when declaring a column of a certain type
27+
# sqlite reports the exact string (including case) used when declaring a column of a certain type.
28+
# the types here should correspond to affinities recognized by SQLite.
29+
# see https://www.sqlite.org/datatype3.html
2830

2931
@classmethod
3032
def convert_text_type(cls, agate_table: agate.Table, col_idx: int) -> str:
@@ -33,23 +35,23 @@ def convert_text_type(cls, agate_table: agate.Table, col_idx: int) -> str:
3335
@classmethod
3436
def convert_number_type(cls, agate_table: agate.Table, col_idx: int) -> str:
3537
decimals = agate_table.aggregate(agate.MaxPrecision(col_idx)) # type: ignore[attr-defined]
36-
return "NUMERIC" if decimals else "INT"
38+
return "REAL" if decimals else "INT"
3739

3840
@classmethod
3941
def convert_boolean_type(cls, agate_table: agate.Table, col_idx: int) -> str:
40-
return "BOOLEAN"
42+
return "INT"
4143

4244
@classmethod
4345
def convert_datetime_type(cls, agate_table: agate.Table, col_idx: int) -> str:
44-
return "TIMESTAMP WITHOUT TIMEZONE"
46+
return "TEXT"
4547

4648
@classmethod
4749
def convert_date_type(cls, agate_table: agate.Table, col_idx: int) -> str:
48-
return "DATE"
50+
return "TEXT"
4951

5052
@classmethod
5153
def convert_time_type(cls, agate_table: agate.Table, col_idx: int) -> str:
52-
return "TIME"
54+
return "TEXT"
5355

5456
def get_live_relation_type(self, relation):
5557
"""
@@ -102,7 +104,7 @@ def get_columns_in_relation(self, relation):
102104
for row in results:
103105
new_row = [
104106
row[1],
105-
row[2] or 'TEXT',
107+
row[2] or 'UNKNOWN',
106108
None,
107109
None,
108110
None

run_tests.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ git clone --depth 1 https://github.com/dbt-labs/jaffle_shop.git
2424

2525
cd jaffle_shop
2626

27-
git pull
28-
2927
mkdir -p /tmp/jaffle_shop
3028

3129
mkdir -p $HOME/.dbt
@@ -57,4 +55,6 @@ jaffle_shop:
5755
5856
EOF
5957

58+
dbt seed
59+
dbt run
6060
dbt docs generate

run_tests_docker.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#!/bin/bash
22

3+
set -e
4+
35
docker build . -t dbt-sqlite
46

57
docker run \

tests/functional/adapter/test_basic.py

Lines changed: 9 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from dbt.tests.adapter.basic.test_snapshot_check_cols import BaseSnapshotCheckCols
1616
from dbt.tests.adapter.basic.test_snapshot_timestamp import BaseSnapshotTimestamp
1717
from dbt.tests.adapter.basic.test_adapter_methods import BaseAdapterMethod
18-
from dbt.tests.adapter.basic.test_docs_generate import BaseDocsGenerate, BaseDocsGenReferences
18+
from dbt.tests.adapter.basic.test_docs_generate import BaseDocsGenerate, BaseDocsGenReferences, models__schema_yml, models__readme_md
1919

2020

2121
class TestSimpleMaterializationsSqlite(BaseSimpleMaterializations):
@@ -58,101 +58,11 @@ class TestBaseAdapterMethodSqlite(BaseAdapterMethod):
5858
pass
5959

6060

61-
@pytest.mark.skip("TODO: fix data type problems")
6261
class TestDocsGenerateSqlite(BaseDocsGenerate):
6362
"""
6463
Change underlying test to avoid having views referencing views in other schemas, which is a no-no in sqlite.
6564
"""
6665

67-
models__schema_yml = """
68-
version: 2
69-
70-
models:
71-
- name: model
72-
description: "The test model"
73-
docs:
74-
show: false
75-
columns:
76-
- name: id
77-
description: The user ID number
78-
tests:
79-
- unique
80-
- not_null
81-
- name: first_name
82-
description: The user's first name
83-
- name: email
84-
description: The user's email
85-
- name: ip_address
86-
description: The user's IP address
87-
- name: updated_at
88-
description: The last time this user's email was updated
89-
tests:
90-
- test.nothing
91-
92-
- name: second_model
93-
description: "The second test model"
94-
docs:
95-
show: false
96-
columns:
97-
- name: id
98-
description: The user ID number
99-
- name: first_name
100-
description: The user's first name
101-
- name: email
102-
description: The user's email
103-
- name: ip_address
104-
description: The user's IP address
105-
- name: updated_at
106-
description: The last time this user's email was updated
107-
108-
109-
sources:
110-
- name: my_source
111-
description: "My source"
112-
loader: a_loader
113-
schema: "{{ var('test_schema') }}"
114-
tables:
115-
- name: my_table
116-
description: "My table"
117-
identifier: seed
118-
quoting:
119-
identifier: True
120-
columns:
121-
- name: id
122-
description: "An ID field"
123-
124-
125-
exposures:
126-
- name: simple_exposure
127-
type: dashboard
128-
depends_on:
129-
- ref('model')
130-
- source('my_source', 'my_table')
131-
owner:
132-
email: something@example.com
133-
- name: notebook_exposure
134-
type: notebook
135-
depends_on:
136-
- ref('model')
137-
- ref('second_model')
138-
owner:
139-
email: something@example.com
140-
name: Some name
141-
description: >
142-
A description of the complex exposure
143-
maturity: medium
144-
meta:
145-
tool: 'my_tool'
146-
languages:
147-
- python
148-
tags: ['my_department']
149-
url: http://example.com/notebook/1
150-
"""
151-
152-
models__readme_md = """
153-
This is a readme.md file with {{ invalid-ish jinja }} in it
154-
"""
155-
15666
models__model_sql = """
15767
{{
15868
config(
@@ -178,9 +88,9 @@ class TestDocsGenerateSqlite(BaseDocsGenerate):
17888
def models(self):
17989
# replace models with
18090
return {
181-
"schema.yml": self.models__schema_yml,
91+
"schema.yml": models__schema_yml,
18292
"second_model.sql": self.models__second_model_sql,
183-
"readme.md": self.models__readme_md,
93+
"readme.md": models__readme_md,
18494
"model.sql": self.models__model_sql,
18595
}
18696

@@ -191,7 +101,7 @@ def expected_catalog(self, project):
191101
role=None,
192102
id_type="INT",
193103
text_type="TEXT",
194-
time_type="DATETIME",
104+
time_type="TEXT",
195105
view_type="view",
196106
table_type="table",
197107
model_stats=no_stats(),
@@ -205,20 +115,21 @@ def expected_catalog(self, project):
205115
return expected_catalog
206116

207117

208-
@pytest.mark.skip("TODO: fix data type problems")
118+
@pytest.mark.skip("TODO: not sure why 'index' values are off by 1")
209119
class TestDocsGenReferencesSqlite(BaseDocsGenReferences):
120+
210121
@pytest.fixture(scope="class")
211122
def expected_catalog(self, project):
212123
return expected_references_catalog(
213124
project,
214125
role=None,
215126
id_type="INT",
216127
text_type="TEXT",
217-
time_type="DATETIME",
128+
time_type="TEXT",
218129
bigint_type="bigint",
219130
view_type="view",
220131
table_type="table",
221132
model_stats=no_stats(),
222-
seed_stats=no_stats(),
223-
view_summary_stats=no_stats(),
133+
#seed_stats=no_stats(),
134+
#view_summary_stats=no_stats(),
224135
)

tests/functional/adapter/utils/test_data_types.py

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,24 @@
66
from dbt.tests.adapter.utils.data_types.test_type_string import BaseTypeString
77
from dbt.tests.adapter.utils.data_types.test_type_timestamp import BaseTypeTimestamp
88

9-
# sqlite's table_info() pragma returns an empty type for columns in views
10-
# so we tweak the models in these tests to materialize as tables
9+
# These tests compare the resulting column types of CASTs against the types
10+
# inferred by agate when loading seeds.
11+
#
12+
# There's a Column class in dbt-core that's used by the default adapter implementation
13+
# of methods like [adapter].type_timestamp() to get a type for CASTs.
14+
#
15+
# Some quirks of SQLite that make these tests challenging:
16+
#
17+
# - a CAST seems to always result in an empty type (i.e. no type affinity) in views,
18+
# but not in a CREATE TABLE AS. So we tweak the tests to materialize models as tables.
19+
#
20+
# - CASTs to an unrecognized type will result in the type being 'NUM' which is a bit
21+
# mysterious.
1122

1223
class TestTypeBigInt(BaseTypeBigInt):
1324
pass
1425

1526

16-
@pytest.mark.skip("TODO: fix this")
1727
class TestTypeFloat(BaseTypeFloat):
1828

1929
models__actual_sql = """
@@ -40,29 +50,39 @@ def models(self):
4050
return {"actual.sql": self.interpolate_macro_namespace(self.models__actual_sql, "type_int")}
4151

4252

43-
@pytest.mark.skip("TODO: fix this")
4453
class TestTypeNumeric(BaseTypeNumeric):
54+
55+
models__actual_sql = """
56+
{{ config(materialized='table') }}
57+
58+
select cast('1.2345' as {{ type_numeric() }}) as numeric_col
59+
"""
60+
61+
@pytest.fixture(scope="class")
62+
def models(self):
63+
return {"actual.sql": self.interpolate_macro_namespace(self.models__actual_sql, "type_numeric")}
64+
4565
def numeric_fixture_type(self):
46-
return "numeric"
66+
return "NUM"
4767

4868

4969
class TestTypeString(BaseTypeString):
50-
pass
51-
52-
53-
@pytest.mark.skip("TODO: fix this")
54-
class TestTypeTimestamp(BaseTypeTimestamp):
5570

5671
models__actual_sql = """
5772
{{ config(materialized='table') }}
5873
59-
select cast('2021-01-01 01:01:01' as {{ type_timestamp() }}) as timestamp_col
74+
select cast('Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
75+
as {{ type_string() }}) as string_col
6076
"""
6177

6278
@pytest.fixture(scope="class")
6379
def models(self):
64-
return {
65-
"actual.sql": self.interpolate_macro_namespace(self.models__actual_sql, "type_timestamp")
66-
}
80+
return {"actual.sql": self.interpolate_macro_namespace(self.models__actual_sql, "type_string")}
81+
6782

68-
83+
# casting to TIMESTAMP results in an 'NUM' type which truncates the original value
84+
# to only the year portion. It's up to the user to properly deal with timestamps
85+
# values from source tables.
86+
@pytest.mark.skip("timestamp not supported in SQLite")
87+
class TestTypeTimestamp(BaseTypeTimestamp):
88+
pass

0 commit comments

Comments
 (0)