meerschaum 2.6.17__py3-none-any.whl → 2.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. meerschaum/actions/delete.py +65 -69
  2. meerschaum/actions/install.py +1 -2
  3. meerschaum/api/routes/_pipes.py +7 -8
  4. meerschaum/config/_default.py +1 -1
  5. meerschaum/config/_paths.py +2 -1
  6. meerschaum/config/_version.py +1 -1
  7. meerschaum/connectors/api/_pipes.py +18 -21
  8. meerschaum/connectors/sql/_instance.py +11 -12
  9. meerschaum/connectors/sql/_pipes.py +122 -78
  10. meerschaum/connectors/sql/_sql.py +43 -8
  11. meerschaum/connectors/valkey/_pipes.py +12 -1
  12. meerschaum/core/Pipe/__init__.py +23 -13
  13. meerschaum/core/Pipe/_attributes.py +25 -1
  14. meerschaum/core/Pipe/_dtypes.py +23 -16
  15. meerschaum/core/Pipe/_sync.py +59 -31
  16. meerschaum/core/Pipe/_verify.py +8 -7
  17. meerschaum/jobs/_Job.py +2 -0
  18. meerschaum/plugins/_Plugin.py +11 -14
  19. meerschaum/utils/daemon/Daemon.py +20 -13
  20. meerschaum/utils/dataframe.py +178 -16
  21. meerschaum/utils/dtypes/__init__.py +149 -14
  22. meerschaum/utils/dtypes/sql.py +41 -7
  23. meerschaum/utils/misc.py +8 -8
  24. meerschaum/utils/sql.py +174 -64
  25. meerschaum/utils/venv/_Venv.py +4 -4
  26. meerschaum/utils/venv/__init__.py +53 -20
  27. {meerschaum-2.6.17.dist-info → meerschaum-2.7.0.dist-info}/METADATA +1 -1
  28. {meerschaum-2.6.17.dist-info → meerschaum-2.7.0.dist-info}/RECORD +34 -34
  29. {meerschaum-2.6.17.dist-info → meerschaum-2.7.0.dist-info}/LICENSE +0 -0
  30. {meerschaum-2.6.17.dist-info → meerschaum-2.7.0.dist-info}/NOTICE +0 -0
  31. {meerschaum-2.6.17.dist-info → meerschaum-2.7.0.dist-info}/WHEEL +0 -0
  32. {meerschaum-2.6.17.dist-info → meerschaum-2.7.0.dist-info}/entry_points.txt +0 -0
  33. {meerschaum-2.6.17.dist-info → meerschaum-2.7.0.dist-info}/top_level.txt +0 -0
  34. {meerschaum-2.6.17.dist-info → meerschaum-2.7.0.dist-info}/zip-safe +0 -0
meerschaum/utils/misc.py CHANGED
@@ -177,14 +177,14 @@ def string_to_dict(
177
177
  keys = _keys[:-1]
178
178
  try:
179
179
  val = ast.literal_eval(_keys[-1])
180
- except Exception as e:
180
+ except Exception:
181
181
  val = str(_keys[-1])
182
182
 
183
183
  c = params_dict
184
184
  for _k in keys[:-1]:
185
185
  try:
186
186
  k = ast.literal_eval(_k)
187
- except Exception as e:
187
+ except Exception:
188
188
  k = str(_k)
189
189
  if k not in c:
190
190
  c[k] = {}
@@ -196,12 +196,12 @@ def string_to_dict(
196
196
 
197
197
 
198
198
  def parse_config_substitution(
199
- value: str,
200
- leading_key: str = 'MRSM',
201
- begin_key: str = '{',
202
- end_key: str = '}',
203
- delimeter: str = ':'
204
- ) -> List[Any]:
199
+ value: str,
200
+ leading_key: str = 'MRSM',
201
+ begin_key: str = '{',
202
+ end_key: str = '}',
203
+ delimeter: str = ':'
204
+ ) -> List[Any]:
205
205
  """
206
206
  Parse Meerschaum substitution syntax
207
207
  E.g. MRSM{value1:value2} => ['value1', 'value2']
meerschaum/utils/sql.py CHANGED
@@ -7,6 +7,7 @@ Flavor-specific SQL tools.
7
7
  """
8
8
 
9
9
  from __future__ import annotations
10
+
10
11
  from datetime import datetime, timezone, timedelta
11
12
  import meerschaum as mrsm
12
13
  from meerschaum.utils.typing import Optional, Dict, Any, Union, List, Iterable, Tuple
@@ -50,10 +51,12 @@ update_queries = {
50
51
  {sets_subquery_none}
51
52
  FROM {target_table_name} AS t
52
53
  INNER JOIN (SELECT DISTINCT {patch_cols_str} FROM {patch_table_name}) AS p
53
- ON {and_subquery_t}
54
+ ON
55
+ {and_subquery_t}
54
56
  WHERE
55
57
  {and_subquery_f}
56
- AND {date_bounds_subquery}
58
+ AND
59
+ {date_bounds_subquery}
57
60
  """,
58
61
  'timescaledb-upsert': """
59
62
  INSERT INTO {target_table_name} ({patch_cols_str})
@@ -82,9 +85,11 @@ update_queries = {
82
85
  'mysql': """
83
86
  UPDATE {target_table_name} AS f
84
87
  JOIN (SELECT DISTINCT {patch_cols_str} FROM {patch_table_name}) AS p
85
- ON {and_subquery_f}
88
+ ON
89
+ {and_subquery_f}
86
90
  {sets_subquery_f}
87
- WHERE {date_bounds_subquery}
91
+ WHERE
92
+ {date_bounds_subquery}
88
93
  """,
89
94
  'mysql-upsert': """
90
95
  INSERT {ignore}INTO {target_table_name} ({patch_cols_str})
@@ -96,9 +101,11 @@ update_queries = {
96
101
  'mariadb': """
97
102
  UPDATE {target_table_name} AS f
98
103
  JOIN (SELECT DISTINCT {patch_cols_str} FROM {patch_table_name}) AS p
99
- ON {and_subquery_f}
104
+ ON
105
+ {and_subquery_f}
100
106
  {sets_subquery_f}
101
- WHERE {date_bounds_subquery}
107
+ WHERE
108
+ {date_bounds_subquery}
102
109
  """,
103
110
  'mariadb-upsert': """
104
111
  INSERT {ignore}INTO {target_table_name} ({patch_cols_str})
@@ -108,34 +115,56 @@ update_queries = {
108
115
  {cols_equal_values}
109
116
  """,
110
117
  'mssql': """
118
+ {with_temp_date_bounds}
111
119
  MERGE {target_table_name} f
112
- USING (SELECT DISTINCT {patch_cols_str} FROM {patch_table_name}) p
113
- ON {and_subquery_f}
114
- AND {date_bounds_subquery}
120
+ USING (SELECT {patch_cols_str} FROM {patch_table_name}) p
121
+ ON
122
+ {and_subquery_f}
123
+ AND
124
+ {date_bounds_subquery}
115
125
  WHEN MATCHED THEN
116
126
  UPDATE
117
127
  {sets_subquery_none};
118
128
  """,
119
- 'mssql-upsert': """
129
+ 'mssql-upsert': [
130
+ "{identity_insert_on}",
131
+ """
132
+ {with_temp_date_bounds}
120
133
  MERGE {target_table_name} f
121
- USING (SELECT DISTINCT {patch_cols_str} FROM {patch_table_name}) p
122
- ON {and_subquery_f}
123
- AND {date_bounds_subquery}
124
- {when_matched_update_sets_subquery_none}
134
+ USING (SELECT {patch_cols_str} FROM {patch_table_name}) p
135
+ ON
136
+ {and_subquery_f}
137
+ AND
138
+ {date_bounds_subquery}{when_matched_update_sets_subquery_none}
125
139
  WHEN NOT MATCHED THEN
126
140
  INSERT ({patch_cols_str})
127
141
  VALUES ({patch_cols_prefixed_str});
128
- """,
142
+ """,
143
+ "{identity_insert_off}",
144
+ ],
129
145
  'oracle': """
130
146
  MERGE INTO {target_table_name} f
131
- USING (SELECT DISTINCT {patch_cols_str} FROM {patch_table_name}) p
147
+ USING (SELECT {patch_cols_str} FROM {patch_table_name}) p
132
148
  ON (
133
149
  {and_subquery_f}
134
- AND {date_bounds_subquery}
150
+ AND
151
+ {date_bounds_subquery}
135
152
  )
136
- WHEN MATCHED THEN
137
- UPDATE
138
- {sets_subquery_none}
153
+ WHEN MATCHED THEN
154
+ UPDATE
155
+ {sets_subquery_none}
156
+ """,
157
+ 'oracle-upsert': """
158
+ MERGE INTO {target_table_name} f
159
+ USING (SELECT {patch_cols_str} FROM {patch_table_name}) p
160
+ ON (
161
+ {and_subquery_f}
162
+ AND
163
+ {date_bounds_subquery}
164
+ ){when_matched_update_sets_subquery_none}
165
+ WHEN NOT MATCHED THEN
166
+ INSERT ({patch_cols_str})
167
+ VALUES ({patch_cols_prefixed_str})
139
168
  """,
140
169
  'sqlite-upsert': """
141
170
  INSERT INTO {target_table_name} ({patch_cols_str})
@@ -323,7 +352,11 @@ columns_indices_queries = {
323
352
  CASE
324
353
  WHEN kc.type = 'PK' THEN 'PRIMARY KEY'
325
354
  ELSE 'INDEX'
326
- END AS [index_type]
355
+ END AS [index_type],
356
+ CASE
357
+ WHEN i.type = 1 THEN CAST(1 AS BIT)
358
+ ELSE CAST(0 AS BIT)
359
+ END AS [clustered]
327
360
  FROM
328
361
  sys.schemas s
329
362
  INNER JOIN sys.tables t
@@ -489,7 +522,8 @@ def dateadd_str(
489
522
  flavor: str = 'postgresql',
490
523
  datepart: str = 'day',
491
524
  number: Union[int, float] = 0,
492
- begin: Union[str, datetime, int] = 'now'
525
+ begin: Union[str, datetime, int] = 'now',
526
+ db_type: Optional[str] = None,
493
527
  ) -> str:
494
528
  """
495
529
  Generate a `DATEADD` clause depending on database flavor.
@@ -528,6 +562,10 @@ def dateadd_str(
528
562
  begin: Union[str, datetime], default `'now'`
529
563
  Base datetime to which to add dateparts.
530
564
 
565
+ db_type: Optional[str], default None
566
+ If provided, cast the datetime string as the type.
567
+ Otherwise, infer this from the input datetime value.
568
+
531
569
  Returns
532
570
  -------
533
571
  The appropriate `DATEADD` string for the corresponding database flavor.
@@ -539,7 +577,7 @@ def dateadd_str(
539
577
  ... begin = datetime(2022, 1, 1, 0, 0),
540
578
  ... number = 1,
541
579
  ... )
542
- "DATEADD(day, 1, CAST('2022-01-01 00:00:00' AS DATETIME))"
580
+ "DATEADD(day, 1, CAST('2022-01-01 00:00:00' AS DATETIME2))"
543
581
  >>> dateadd_str(
544
582
  ... flavor = 'postgresql',
545
583
  ... begin = datetime(2022, 1, 1, 0, 0),
@@ -582,7 +620,7 @@ def dateadd_str(
582
620
  )
583
621
 
584
622
  dt_is_utc = begin_time.tzinfo is not None if begin_time is not None else '+' in str(begin)
585
- db_type = get_db_type_from_pd_type(
623
+ db_type = db_type or get_db_type_from_pd_type(
586
624
  ('datetime64[ns, UTC]' if dt_is_utc else 'datetime64[ns]'),
587
625
  flavor=flavor,
588
626
  )
@@ -707,7 +745,7 @@ def get_distinct_col_count(
707
745
  result = connector.value(_meta_query, debug=debug)
708
746
  try:
709
747
  return int(result)
710
- except Exception as e:
748
+ except Exception:
711
749
  return None
712
750
 
713
751
 
@@ -717,12 +755,15 @@ def sql_item_name(item: str, flavor: str, schema: Optional[str] = None) -> str:
717
755
 
718
756
  Parameters
719
757
  ----------
720
- item: str :
758
+ item: str
721
759
  The database item (table, view, etc.) in need of quotes.
722
760
 
723
- flavor: str :
761
+ flavor: str
724
762
  The database flavor (`'postgresql'`, `'mssql'`, `'sqllite'`, etc.).
725
763
 
764
+ schema: Optional[str], default None
765
+ If provided, prefix the table name with the schema.
766
+
726
767
  Returns
727
768
  -------
728
769
  A `str` which contains the input `item` wrapped in the corresponding escape characters.
@@ -754,6 +795,8 @@ def sql_item_name(item: str, flavor: str, schema: Optional[str] = None) -> str:
754
795
  ### NOTE: SQLite does not support schemas.
755
796
  if flavor == 'sqlite':
756
797
  schema = None
798
+ elif flavor == 'mssql' and str(item).startswith('#'):
799
+ schema = None
757
800
 
758
801
  schema_prefix = (
759
802
  (wrappers[0] + schema + wrappers[1] + '.')
@@ -1056,7 +1099,7 @@ def get_sqlalchemy_table(
1056
1099
  connector.metadata,
1057
1100
  **table_kwargs
1058
1101
  )
1059
- except sqlalchemy.exc.NoSuchTableError as e:
1102
+ except sqlalchemy.exc.NoSuchTableError:
1060
1103
  warn(f"Table '{truncated_table_name}' does not exist in '{connector}'.")
1061
1104
  return None
1062
1105
  return tables[truncated_table_name]
@@ -1109,6 +1152,7 @@ def get_table_cols_types(
1109
1152
  -------
1110
1153
  A dictionary mapping column names to data types.
1111
1154
  """
1155
+ import textwrap
1112
1156
  from meerschaum.connectors import SQLConnector
1113
1157
  sqlalchemy = mrsm.attempt_import('sqlalchemy')
1114
1158
  flavor = flavor or getattr(connectable, 'flavor', None)
@@ -1134,7 +1178,7 @@ def get_table_cols_types(
1134
1178
  )
1135
1179
 
1136
1180
  cols_types_query = sqlalchemy.text(
1137
- columns_types_queries.get(
1181
+ textwrap.dedent(columns_types_queries.get(
1138
1182
  flavor,
1139
1183
  columns_types_queries['default']
1140
1184
  ).format(
@@ -1145,7 +1189,7 @@ def get_table_cols_types(
1145
1189
  table_upper=table_upper,
1146
1190
  table_upper_trunc=table_upper_trunc,
1147
1191
  db_prefix=db_prefix,
1148
- )
1192
+ )).lstrip().rstrip()
1149
1193
  )
1150
1194
 
1151
1195
  cols = ['database', 'schema', 'table', 'column', 'type']
@@ -1259,6 +1303,7 @@ def get_table_cols_indices(
1259
1303
  -------
1260
1304
  A dictionary mapping column names to a list of indices.
1261
1305
  """
1306
+ import textwrap
1262
1307
  from collections import defaultdict
1263
1308
  from meerschaum.connectors import SQLConnector
1264
1309
  sqlalchemy = mrsm.attempt_import('sqlalchemy')
@@ -1285,7 +1330,7 @@ def get_table_cols_indices(
1285
1330
  )
1286
1331
 
1287
1332
  cols_indices_query = sqlalchemy.text(
1288
- columns_indices_queries.get(
1333
+ textwrap.dedent(columns_indices_queries.get(
1289
1334
  flavor,
1290
1335
  columns_indices_queries['default']
1291
1336
  ).format(
@@ -1297,10 +1342,12 @@ def get_table_cols_indices(
1297
1342
  table_upper_trunc=table_upper_trunc,
1298
1343
  db_prefix=db_prefix,
1299
1344
  schema=schema,
1300
- )
1345
+ )).lstrip().rstrip()
1301
1346
  )
1302
1347
 
1303
1348
  cols = ['database', 'schema', 'table', 'column', 'index', 'index_type']
1349
+ if flavor == 'mssql':
1350
+ cols.append('clustered')
1304
1351
  result_cols_ix = dict(enumerate(cols))
1305
1352
 
1306
1353
  debug_kwargs = {'debug': debug} if isinstance(connectable, SQLConnector) else {}
@@ -1341,7 +1388,6 @@ def get_table_cols_indices(
1341
1388
  )
1342
1389
  )
1343
1390
  ]
1344
-
1345
1391
  ### NOTE: This may return incorrect columns if the schema is not explicitly stated.
1346
1392
  if cols_types_docs and not cols_types_docs_filtered:
1347
1393
  cols_types_docs_filtered = cols_types_docs
@@ -1357,12 +1403,13 @@ def get_table_cols_indices(
1357
1403
  else doc['column']
1358
1404
  )
1359
1405
  )
1360
- cols_indices[col].append(
1361
- {
1362
- 'name': doc.get('index', None),
1363
- 'type': doc.get('index_type', None),
1364
- }
1365
- )
1406
+ index_doc = {
1407
+ 'name': doc.get('index', None),
1408
+ 'type': doc.get('index_type', None)
1409
+ }
1410
+ if flavor == 'mssql':
1411
+ index_doc['clustered'] = doc.get('clustered', None)
1412
+ cols_indices[col].append(index_doc)
1366
1413
 
1367
1414
  return dict(cols_indices)
1368
1415
  except Exception as e:
@@ -1383,6 +1430,7 @@ def get_update_queries(
1383
1430
  datetime_col: Optional[str] = None,
1384
1431
  schema: Optional[str] = None,
1385
1432
  patch_schema: Optional[str] = None,
1433
+ identity_insert: bool = False,
1386
1434
  debug: bool = False,
1387
1435
  ) -> List[str]:
1388
1436
  """
@@ -1420,6 +1468,10 @@ def get_update_queries(
1420
1468
  If provided, use this schema when quoting the patch table.
1421
1469
  Defaults to `schema`.
1422
1470
 
1471
+ identity_insert: bool, default False
1472
+ If `True`, include `SET IDENTITY_INSERT` queries before and after the update queries.
1473
+ Only applies for MSSQL upserts.
1474
+
1423
1475
  debug: bool, default False
1424
1476
  Verbosity toggle.
1425
1477
 
@@ -1427,9 +1479,11 @@ def get_update_queries(
1427
1479
  -------
1428
1480
  A list of query strings to perform the update operation.
1429
1481
  """
1482
+ import textwrap
1430
1483
  from meerschaum.connectors import SQLConnector
1431
1484
  from meerschaum.utils.debug import dprint
1432
- from meerschaum.utils.dtypes.sql import DB_FLAVORS_CAST_DTYPES
1485
+ from meerschaum.utils.dtypes import are_dtypes_equal
1486
+ from meerschaum.utils.dtypes.sql import DB_FLAVORS_CAST_DTYPES, get_pd_type_from_db_type
1433
1487
  flavor = flavor or (connectable.flavor if isinstance(connectable, SQLConnector) else None)
1434
1488
  if not flavor:
1435
1489
  raise ValueError("Provide a flavor if using a SQLAlchemy session.")
@@ -1522,21 +1576,35 @@ def get_update_queries(
1522
1576
  def sets_subquery(l_prefix: str, r_prefix: str):
1523
1577
  if not value_cols:
1524
1578
  return ''
1579
+
1580
+ cast_func_cols = {
1581
+ c_name: (
1582
+ ('', '', '')
1583
+ if (
1584
+ flavor == 'oracle'
1585
+ and are_dtypes_equal(get_pd_type_from_db_type(c_type), 'bytes')
1586
+ )
1587
+ else (
1588
+ ('CAST(', f" AS {c_type.replace('_', ' ')}", ')')
1589
+ if flavor != 'sqlite'
1590
+ else ('', '', '')
1591
+ )
1592
+ )
1593
+ for c_name, c_type in value_cols
1594
+ }
1525
1595
  return 'SET ' + ',\n'.join([
1526
1596
  (
1527
1597
  l_prefix + sql_item_name(c_name, flavor, None)
1528
1598
  + ' = '
1529
- + ('CAST(' if flavor != 'sqlite' else '')
1530
- + r_prefix
1531
- + sql_item_name(c_name, flavor, None)
1532
- + (' AS ' if flavor != 'sqlite' else '')
1533
- + (c_type.replace('_', ' ') if flavor != 'sqlite' else '')
1534
- + (')' if flavor != 'sqlite' else '')
1599
+ + cast_func_cols[c_name][0]
1600
+ + r_prefix + sql_item_name(c_name, flavor, None)
1601
+ + cast_func_cols[c_name][1]
1602
+ + cast_func_cols[c_name][2]
1535
1603
  ) for c_name, c_type in value_cols
1536
1604
  ])
1537
1605
 
1538
1606
  def and_subquery(l_prefix: str, r_prefix: str):
1539
- return '\nAND\n'.join([
1607
+ return '\n AND\n '.join([
1540
1608
  (
1541
1609
  "COALESCE("
1542
1610
  + l_prefix
@@ -1544,7 +1612,7 @@ def get_update_queries(
1544
1612
  + ", "
1545
1613
  + get_null_replacement(c_type, flavor)
1546
1614
  + ")"
1547
- + ' = '
1615
+ + '\n =\n '
1548
1616
  + "COALESCE("
1549
1617
  + r_prefix
1550
1618
  + sql_item_name(c_name, flavor, None)
@@ -1554,22 +1622,39 @@ def get_update_queries(
1554
1622
  ) for c_name, c_type in join_cols_types
1555
1623
  ])
1556
1624
 
1625
+ skip_query_val = ""
1557
1626
  target_table_name = sql_item_name(target, flavor, schema)
1558
1627
  patch_table_name = sql_item_name(patch, flavor, patch_schema)
1559
1628
  dt_col_name = sql_item_name(datetime_col, flavor, None) if datetime_col else None
1629
+ date_bounds_table = patch_table_name if flavor != 'mssql' else '[date_bounds]'
1630
+ min_dt_col_name = f"MIN({dt_col_name})" if flavor != 'mssql' else '[Min_dt]'
1631
+ max_dt_col_name = f"MAX({dt_col_name})" if flavor != 'mssql' else '[Max_dt]'
1560
1632
  date_bounds_subquery = (
1561
- f"""
1562
- f.{dt_col_name} >= (SELECT MIN({dt_col_name}) FROM {patch_table_name})
1563
- AND f.{dt_col_name} <= (SELECT MAX({dt_col_name}) FROM {patch_table_name})
1564
- """
1633
+ f"""f.{dt_col_name} >= (SELECT {min_dt_col_name} FROM {date_bounds_table})
1634
+ AND
1635
+ f.{dt_col_name} <= (SELECT {max_dt_col_name} FROM {date_bounds_table})"""
1565
1636
  if datetime_col
1566
1637
  else "1 = 1"
1567
1638
  )
1639
+ with_temp_date_bounds = f"""WITH [date_bounds] AS (
1640
+ SELECT MIN({dt_col_name}) AS {min_dt_col_name}, MAX({dt_col_name}) AS {max_dt_col_name}
1641
+ FROM {patch_table_name}
1642
+ )""" if datetime_col else ""
1643
+ identity_insert_on = (
1644
+ f"SET IDENTITY_INSERT {target_table_name} ON"
1645
+ if identity_insert
1646
+ else skip_query_val
1647
+ )
1648
+ identity_insert_off = (
1649
+ f"SET IDENTITY_INSERT {target_table_name} OFF"
1650
+ if identity_insert
1651
+ else skip_query_val
1652
+ )
1568
1653
 
1569
1654
  ### NOTE: MSSQL upserts must exclude the update portion if only upserting indices.
1570
1655
  when_matched_update_sets_subquery_none = "" if not value_cols else (
1571
- "WHEN MATCHED THEN"
1572
- f" UPDATE {sets_subquery('', 'p.')}"
1656
+ "\n WHEN MATCHED THEN\n"
1657
+ f" UPDATE {sets_subquery('', 'p.')}"
1573
1658
  )
1574
1659
 
1575
1660
  cols_equal_values = '\n,'.join(
@@ -1585,8 +1670,8 @@ def get_update_queries(
1585
1670
  )
1586
1671
  ignore = "IGNORE " if not value_cols else ""
1587
1672
 
1588
- return [
1589
- base_query.format(
1673
+ formatted_queries = [
1674
+ textwrap.dedent(base_query.format(
1590
1675
  sets_subquery_none=sets_subquery('', 'p.'),
1591
1676
  sets_subquery_none_excluded=sets_subquery('', 'EXCLUDED.'),
1592
1677
  sets_subquery_f=sets_subquery('f.', 'p.'),
@@ -1604,10 +1689,16 @@ def get_update_queries(
1604
1689
  cols_equal_values=cols_equal_values,
1605
1690
  on_duplicate_key_update=on_duplicate_key_update,
1606
1691
  ignore=ignore,
1607
- )
1692
+ with_temp_date_bounds=with_temp_date_bounds,
1693
+ identity_insert_on=identity_insert_on,
1694
+ identity_insert_off=identity_insert_off,
1695
+ )).lstrip().rstrip()
1608
1696
  for base_query in base_queries
1609
1697
  ]
1610
1698
 
1699
+ ### NOTE: Allow for skipping some queries.
1700
+ return [query for query in formatted_queries if query]
1701
+
1611
1702
 
1612
1703
  def get_null_replacement(typ: str, flavor: str) -> str:
1613
1704
  """
@@ -1645,11 +1736,14 @@ def get_null_replacement(typ: str, flavor: str) -> str:
1645
1736
  )
1646
1737
  return f'CAST({val_to_cast} AS {bool_typ})'
1647
1738
  if 'time' in typ.lower() or 'date' in typ.lower():
1648
- return dateadd_str(flavor=flavor, begin='1900-01-01')
1739
+ db_type = typ if typ.isupper() else None
1740
+ return dateadd_str(flavor=flavor, begin='1900-01-01', db_type=db_type)
1649
1741
  if 'float' in typ.lower() or 'double' in typ.lower() or typ.lower() in ('decimal',):
1650
1742
  return '-987654321.0'
1651
1743
  if flavor == 'oracle' and typ.lower().split('(', maxsplit=1)[0] == 'char':
1652
1744
  return "'-987654321'"
1745
+ if flavor == 'oracle' and typ.lower() in ('blob', 'bytes'):
1746
+ return '00'
1653
1747
  if typ.lower() in ('uniqueidentifier', 'guid', 'uuid'):
1654
1748
  magic_val = 'DEADBEEF-ABBA-BABE-CAFE-DECAFC0FFEE5'
1655
1749
  if flavor == 'mssql':
@@ -1857,7 +1951,17 @@ def _get_create_table_query_from_dtypes(
1857
1951
 
1858
1952
  table_name = sql_item_name(new_table, schema=schema, flavor=flavor)
1859
1953
  primary_key_name = sql_item_name(primary_key, flavor) if primary_key else None
1954
+ primary_key_constraint_name = (
1955
+ sql_item_name(f'PK_{new_table}', flavor, None)
1956
+ if primary_key
1957
+ else None
1958
+ )
1860
1959
  datetime_column_name = sql_item_name(datetime_column, flavor) if datetime_column else None
1960
+ primary_key_clustered = (
1961
+ "CLUSTERED"
1962
+ if not datetime_column or datetime_column == primary_key
1963
+ else "NONCLUSTERED"
1964
+ )
1861
1965
  query = f"CREATE TABLE {table_name} ("
1862
1966
  if primary_key:
1863
1967
  col_db_type = cols_types[0][1]
@@ -1877,6 +1981,8 @@ def _get_create_table_query_from_dtypes(
1877
1981
  query += f"\n {col_name} {col_db_type} {auto_increment_str} PRIMARY KEY,"
1878
1982
  elif flavor == 'timescaledb' and datetime_column and datetime_column != primary_key:
1879
1983
  query += f"\n {col_name} {col_db_type}{auto_increment_str} NOT NULL,"
1984
+ elif flavor == 'mssql':
1985
+ query += f"\n {col_name} {col_db_type}{auto_increment_str} NOT NULL,"
1880
1986
  else:
1881
1987
  query += f"\n {col_name} {col_db_type} PRIMARY KEY{auto_increment_str} NOT NULL,"
1882
1988
 
@@ -1892,6 +1998,10 @@ def _get_create_table_query_from_dtypes(
1892
1998
  and datetime_column != primary_key
1893
1999
  ):
1894
2000
  query += f"\n PRIMARY KEY({datetime_column_name}, {primary_key_name}),"
2001
+
2002
+ if flavor == 'mssql' and primary_key:
2003
+ query += f"\n CONSTRAINT {primary_key_constraint_name} PRIMARY KEY {primary_key_clustered} ({primary_key_name}),"
2004
+
1895
2005
  query = query[:-1]
1896
2006
  query += "\n)"
1897
2007
 
@@ -1912,12 +2022,11 @@ def _get_create_table_query_from_cte(
1912
2022
  Create a new table from a CTE query.
1913
2023
  """
1914
2024
  import textwrap
1915
- from meerschaum.utils.dtypes.sql import AUTO_INCREMENT_COLUMN_FLAVORS
1916
2025
  create_cte = 'create_query'
1917
2026
  create_cte_name = sql_item_name(create_cte, flavor, None)
1918
2027
  new_table_name = sql_item_name(new_table, flavor, schema)
1919
2028
  primary_key_constraint_name = (
1920
- sql_item_name(f'pk_{new_table}', flavor, None)
2029
+ sql_item_name(f'PK_{new_table}', flavor, None)
1921
2030
  if primary_key
1922
2031
  else None
1923
2032
  )
@@ -1926,6 +2035,7 @@ def _get_create_table_query_from_cte(
1926
2035
  if primary_key
1927
2036
  else None
1928
2037
  )
2038
+ primary_key_clustered = "CLUSTERED" if not datetime_column else "NONCLUSTERED"
1929
2039
  datetime_column_name = (
1930
2040
  sql_item_name(datetime_column, flavor)
1931
2041
  if datetime_column
@@ -1933,7 +2043,7 @@ def _get_create_table_query_from_cte(
1933
2043
  )
1934
2044
  if flavor in ('mssql',):
1935
2045
  query = query.lstrip()
1936
- if 'with ' in query.lower():
2046
+ if query.lower().startswith('with '):
1937
2047
  final_select_ix = query.lower().rfind('select')
1938
2048
  create_table_query = (
1939
2049
  query[:final_select_ix].rstrip() + ',\n'
@@ -1951,7 +2061,7 @@ def _get_create_table_query_from_cte(
1951
2061
 
1952
2062
  alter_type_query = f"""
1953
2063
  ALTER TABLE {new_table_name}
1954
- ADD CONSTRAINT {primary_key_constraint_name} PRIMARY KEY ({primary_key_name})
2064
+ ADD CONSTRAINT {primary_key_constraint_name} PRIMARY KEY {primary_key_clustered} ({primary_key_name})
1955
2065
  """
1956
2066
  elif flavor in (None,):
1957
2067
  create_table_query = f"""
@@ -1999,11 +2109,11 @@ def _get_create_table_query_from_cte(
1999
2109
  ADD PRIMARY KEY ({primary_key_name})
2000
2110
  """
2001
2111
 
2002
- create_table_query = textwrap.dedent(create_table_query)
2112
+ create_table_query = textwrap.dedent(create_table_query).lstrip().rstrip()
2003
2113
  if not primary_key:
2004
2114
  return [create_table_query]
2005
2115
 
2006
- alter_type_query = textwrap.dedent(alter_type_query)
2116
+ alter_type_query = textwrap.dedent(alter_type_query).lstrip().rstrip()
2007
2117
 
2008
2118
  return [
2009
2119
  create_table_query,
@@ -34,10 +34,10 @@ class Venv:
34
34
  """
35
35
 
36
36
  def __init__(
37
- self,
38
- venv: Union[str, 'meerschaum.plugins.Plugin', None] = 'mrsm',
39
- debug: bool = False,
40
- ) -> None:
37
+ self,
38
+ venv: Union[str, 'meerschaum.plugins.Plugin', None] = 'mrsm',
39
+ debug: bool = False,
40
+ ) -> None:
41
41
  from meerschaum.utils.venv import activate_venv, deactivate_venv, active_venvs
42
42
  ### For some weird threading issue,
43
43
  ### we can't use `isinstance` here.