@@ -104,111 +104,161 @@ def test_table_clustering_fields_dialect_option_type_error(faux_conn):
104104 )
105105
106106
107- def test_table_time_partitioning_dialect_option (faux_conn ):
108- # expect table creation to fail as SQLite does not support partitioned tables
109- with pytest .raises (sqlite3 .OperationalError ):
110- setup_table (
111- faux_conn ,
112- "some_table" ,
113- sqlalchemy .Column ("id" , sqlalchemy .Integer ),
114- sqlalchemy .Column ("createdAt" , sqlalchemy .DateTime ),
115- bigquery_time_partitioning = TimePartitioning (),
116- )
107+ @pytest .mark .parametrize (
108+ "column_dtype,time_partitioning_type,func_name" ,
109+ [
110+ # DATE dtype
111+ pytest .param (
112+ sqlalchemy .DATE ,
113+ TimePartitioningType .HOUR , # Only MONTH/YEAR are permitted in BigQuery
114+ "DATE_TRUNC" ,
115+ marks = pytest .mark .xfail ,
116+ ),
117+ pytest .param (
118+ sqlalchemy .DATE ,
119+ TimePartitioningType .DAY , # Only MONTH/YEAR are permitted in BigQuery
120+ "DATE_TRUNC" ,
121+ marks = pytest .mark .xfail ,
122+ ),
123+ (sqlalchemy .DATE , TimePartitioningType .MONTH , "DATE_TRUNC" ),
124+ (sqlalchemy .DATE , TimePartitioningType .YEAR , "DATE_TRUNC" ),
125+ # TIMESTAMP dtype
126+ (sqlalchemy .TIMESTAMP , TimePartitioningType .HOUR , "TIMESTAMP_TRUNC" ),
127+ (sqlalchemy .TIMESTAMP , TimePartitioningType .DAY , "TIMESTAMP_TRUNC" ),
128+ (sqlalchemy .TIMESTAMP , TimePartitioningType .MONTH , "TIMESTAMP_TRUNC" ),
129+ (sqlalchemy .TIMESTAMP , TimePartitioningType .YEAR , "TIMESTAMP_TRUNC" ),
130+ # DATETIME dtype
131+ (sqlalchemy .DATETIME , TimePartitioningType .HOUR , "DATETIME_TRUNC" ),
132+ (sqlalchemy .DATETIME , TimePartitioningType .DAY , "DATETIME_TRUNC" ),
133+ (sqlalchemy .DATETIME , TimePartitioningType .MONTH , "DATETIME_TRUNC" ),
134+ (sqlalchemy .DATETIME , TimePartitioningType .YEAR , "DATETIME_TRUNC" ),
135+ # TimePartitioning.type_ == None
136+ (sqlalchemy .DATETIME , None , "DATETIME_TRUNC" ),
137+ ],
138+ )
139+ def test_table_time_partitioning_given_field_and_type__dialect_options (
140+ faux_conn , column_dtype , time_partitioning_type , func_name
141+ ):
142+ """NOTE: Expect table creation to fail as SQLite does not support
143+ partitioned tables, despite that, we are still able to test the generation
144+ of SQL statements.
117145
118- assert " " .join (faux_conn .test_data ["execute" ][- 1 ][0 ].strip ().split ()) == (
119- "CREATE TABLE `some_table` ( `id` INT64, `createdAt` DATETIME )"
120- " PARTITION BY DATE_TRUNC(_PARTITIONDATE, DAY)"
121- )
146+ Each parametrization ensures that the appropriate function is generated
147+ depending on whether the column datatype is DATE, TIMESTAMP, DATETIME and
148+ whether the TimePartitioningType is HOUR, DAY, MONTH, YEAR.
122149
150+ `DATE_TRUNC` only returns a result if TimePartitioningType is DAY, MONTH,
151+ YEAR. BigQuery cannot partition on DATE by HOUR, so that is expected to
152+ xfail.
123153
124- def test_table_require_partition_filter_dialect_option (faux_conn ):
125- # expect table creation to fail as SQLite does not support partitioned tables
126- with pytest .raises (sqlite3 .OperationalError ):
127- setup_table (
128- faux_conn ,
129- "some_table" ,
130- sqlalchemy .Column ("createdAt" , sqlalchemy .DateTime ),
131- bigquery_time_partitioning = TimePartitioning (field = "createdAt" ),
132- bigquery_require_partition_filter = True ,
133- )
154+ A distinguishing characteristic of this test is we provide an argument to
155+ the TimePartitioning class for both field and type_.
134156
135- assert " " .join (faux_conn .test_data ["execute" ][- 1 ][0 ].strip ().split ()) == (
136- "CREATE TABLE `some_table` ( `createdAt` DATETIME )"
137- " PARTITION BY DATE_TRUNC(createdAt, DAY)"
138- " OPTIONS(require_partition_filter=true)"
139- )
157+ Special case: IF time_partitioning_type is None, the __init__() in the
158+ TimePartitioning class will overwrite it with TimePartitioningType.DAY as
159+ the default.
160+ """
140161
162+ if time_partitioning_type is None :
163+ time_partitioning_type = TimePartitioningType .DAY
141164
142- def test_table_time_partitioning_with_field_dialect_option (faux_conn ):
143- # expect table creation to fail as SQLite does not support partitioned tables
144165 with pytest .raises (sqlite3 .OperationalError ):
145166 setup_table (
146167 faux_conn ,
147168 "some_table" ,
148169 sqlalchemy .Column ("id" , sqlalchemy .Integer ),
149- sqlalchemy .Column ("createdAt" , sqlalchemy .DateTime ),
150- bigquery_time_partitioning = TimePartitioning (field = "createdAt" ),
170+ sqlalchemy .Column ("createdAt" , column_dtype ),
171+ bigquery_time_partitioning = TimePartitioning (
172+ field = "createdAt" , type_ = time_partitioning_type
173+ ),
151174 )
152175
153- assert " " .join (faux_conn .test_data ["execute" ][- 1 ][0 ].strip ().split ()) == (
154- "CREATE TABLE `some_table` ( `id` INT64, `createdAt` DATETIME )"
155- " PARTITION BY DATE_TRUNC(createdAt, DAY)"
176+ result = " " .join (faux_conn .test_data ["execute" ][- 1 ][0 ].strip ().split ())
177+ expected = (
178+ f"CREATE TABLE `some_table` ( `id` INT64, `createdAt` { column_dtype .__visit_name__ } )"
179+ f" PARTITION BY { func_name } (createdAt, { time_partitioning_type } )"
156180 )
181+ assert result == expected
157182
158183
159- def test_table_time_partitioning_by_month_dialect_option (faux_conn ):
160- # expect table creation to fail as SQLite does not support partitioned tables
161- with pytest .raises (sqlite3 .OperationalError ):
162- setup_table (
163- faux_conn ,
164- "some_table" ,
165- sqlalchemy .Column ("id" , sqlalchemy .Integer ),
166- sqlalchemy .Column ("createdAt" , sqlalchemy .DateTime ),
167- bigquery_time_partitioning = TimePartitioning (
168- field = "createdAt" ,
169- type_ = TimePartitioningType .MONTH ,
170- ),
171- )
184+ def test_table_time_partitioning_given_field_but_no_type__dialect_option (faux_conn ):
185+ """Expect table creation to fail as SQLite does not support partitioned tables
172186
173- assert " " .join (faux_conn .test_data ["execute" ][- 1 ][0 ].strip ().split ()) == (
174- "CREATE TABLE `some_table` ( `id` INT64, `createdAt` DATETIME )"
175- " PARTITION BY DATE_TRUNC(createdAt, MONTH)"
176- )
187+ Confirms that if the column datatype is DATETIME but no TimePartitioning.type_
188+ has been supplied, the system will default to DAY.
177189
190+ A distinguishing characteristic of this test is we provide an argument to
191+ the TimePartitioning class for field but not type_.
192+ """
178193
179- def test_table_time_partitioning_with_timestamp_dialect_option (faux_conn ):
180- # expect table creation to fail as SQLite does not support partitioned tables
181194 with pytest .raises (sqlite3 .OperationalError ):
182195 setup_table (
183196 faux_conn ,
184197 "some_table" ,
185198 sqlalchemy .Column ("id" , sqlalchemy .Integer ),
186- sqlalchemy .Column ("createdAt" , sqlalchemy .TIMESTAMP ),
199+ sqlalchemy .Column ("createdAt" , sqlalchemy .DateTime ),
187200 bigquery_time_partitioning = TimePartitioning (field = "createdAt" ),
188201 )
189-
190- assert " " . join ( faux_conn . test_data [ "execute" ][ - 1 ][ 0 ]. strip (). split ()) = = (
191- "CREATE TABLE `some_table` ( `id` INT64, `createdAt` TIMESTAMP )"
192- " PARTITION BY TIMESTAMP_TRUNC (createdAt, DAY)"
202+ result = " " . join ( faux_conn . test_data [ "execute" ][ - 1 ][ 0 ]. strip (). split ())
203+ expected = (
204+ "CREATE TABLE `some_table` ( `id` INT64, `createdAt` DATETIME )"
205+ " PARTITION BY DATETIME_TRUNC (createdAt, DAY)"
193206 )
207+ assert result == expected
194208
195209
196- def test_table_time_partitioning_with_date_dialect_option (faux_conn ):
197- # expect table creation to fail as SQLite does not support partitioned tables
210+ @pytest .mark .parametrize (
211+ "column_dtype,time_partitioning_type" ,
212+ [
213+ pytest .param (
214+ sqlalchemy .DATE ,
215+ TimePartitioningType .HOUR ,
216+ marks = pytest .mark .xfail ,
217+ ),
218+ (sqlalchemy .DATE , TimePartitioningType .DAY ),
219+ (sqlalchemy .DATE , TimePartitioningType .MONTH ),
220+ (sqlalchemy .DATE , TimePartitioningType .YEAR ),
221+ ],
222+ )
223+ def test_table_time_partitioning_given_type__but_no_field_dialect_option (
224+ faux_conn ,
225+ column_dtype ,
226+ time_partitioning_type ,
227+ ):
228+ """NOTE: Expect table creation to fail as SQLite does not support
229+ partitioned tables, despite that, we are still able to test the generation
230+ of SQL statements
231+
232+ If the `field` argument to TimePartitioning() is not provided, it defaults to
233+ None. That causes the pseudocolumn "_PARTITIONDATE" to be used by default as
234+ the column to partition by.
235+
236+ _PARTITIONTIME only returns a result if TimePartitioningType is DAY, MONTH,
237+ YEAR. BigQuery cannot partition on _PARTITIONDATE by HOUR, so that is
238+ expected to xfail.
239+
240+ A distinguishing characteristic of this test is we provide an argument to
241+ the TimePartitioning class for type_ but not field.
242+ """
243+
198244 with pytest .raises (sqlite3 .OperationalError ):
199245 setup_table (
200246 faux_conn ,
201247 "some_table_2" ,
202248 sqlalchemy .Column ("id" , sqlalchemy .Integer ),
203- sqlalchemy .Column ("createdAt" , sqlalchemy . DATE ),
204- bigquery_time_partitioning = TimePartitioning (field = "createdAt" ),
249+ sqlalchemy .Column ("createdAt" , column_dtype ),
250+ bigquery_time_partitioning = TimePartitioning (type_ = time_partitioning_type ),
205251 )
206252
207253 # confirm that the following code creates the correct SQL string
208- assert " " .join (faux_conn .test_data ["execute" ][- 1 ][0 ].strip ().split ()) == (
209- "CREATE TABLE `some_table_2` ( `id` INT64, `createdAt` DATE )"
210- " PARTITION BY createdAt"
254+ result = " " .join (faux_conn .test_data ["execute" ][- 1 ][0 ].strip ().split ())
255+
256+ # We need two versions of expected depending on whether we use _PARTITIONDATE
257+ expected = (
258+ f"CREATE TABLE `some_table_2` ( `id` INT64, `createdAt` { column_dtype .__visit_name__ } )"
259+ f" PARTITION BY _PARTITIONDATE"
211260 )
261+ assert result == expected
212262
213263
214264def test_table_time_partitioning_dialect_option_partition_expiration_days (faux_conn ):
@@ -227,7 +277,7 @@ def test_table_time_partitioning_dialect_option_partition_expiration_days(faux_c
227277
228278 assert " " .join (faux_conn .test_data ["execute" ][- 1 ][0 ].strip ().split ()) == (
229279 "CREATE TABLE `some_table` ( `createdAt` DATETIME )"
230- " PARTITION BY DATE_TRUNC (createdAt, DAY)"
280+ " PARTITION BY DATETIME_TRUNC (createdAt, DAY)"
231281 " OPTIONS(partition_expiration_days=0.25)"
232282 )
233283
@@ -400,13 +450,16 @@ def test_table_all_dialect_option(faux_conn):
400450 ),
401451 )
402452
403- assert " " .join (faux_conn .test_data ["execute" ][- 1 ][0 ].strip ().split ()) == (
453+ result = " " .join (faux_conn .test_data ["execute" ][- 1 ][0 ].strip ().split ())
454+ expected = (
404455 "CREATE TABLE `some_table` ( `id` INT64, `country` STRING, `town` STRING, `createdAt` DATETIME )"
405- " PARTITION BY DATE_TRUNC (createdAt, DAY)"
456+ " PARTITION BY DATETIME_TRUNC (createdAt, DAY)"
406457 " CLUSTER BY country, town"
407458 " OPTIONS(partition_expiration_days=30.0, expiration_timestamp=TIMESTAMP '2038-01-01 00:00:00+00:00', require_partition_filter=true, default_rounding_mode='ROUND_HALF_EVEN')"
408459 )
409460
461+ assert result == expected
462+
410463
411464def test_validate_friendly_name_value_type (ddl_compiler ):
412465 # expect option value to be transformed as a string expression
0 commit comments