meerschaum 2.5.1__py3-none-any.whl → 2.6.0.dev1__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 (30) hide show
  1. meerschaum/_internal/arguments/_parser.py +6 -1
  2. meerschaum/actions/edit.py +6 -6
  3. meerschaum/actions/sql.py +12 -11
  4. meerschaum/config/_edit.py +46 -19
  5. meerschaum/config/_read_config.py +20 -9
  6. meerschaum/config/_version.py +1 -1
  7. meerschaum/config/stack/__init__.py +1 -1
  8. meerschaum/connectors/sql/_pipes.py +80 -24
  9. meerschaum/connectors/sql/_sql.py +29 -10
  10. meerschaum/connectors/valkey/_pipes.py +1 -1
  11. meerschaum/core/Pipe/__init__.py +8 -9
  12. meerschaum/core/Pipe/_attributes.py +33 -11
  13. meerschaum/core/Pipe/_data.py +26 -7
  14. meerschaum/core/Pipe/_dtypes.py +4 -4
  15. meerschaum/core/Pipe/_fetch.py +1 -1
  16. meerschaum/core/Pipe/_sync.py +16 -4
  17. meerschaum/core/Pipe/_verify.py +1 -1
  18. meerschaum/utils/dataframe.py +56 -29
  19. meerschaum/utils/dtypes/__init__.py +16 -5
  20. meerschaum/utils/dtypes/sql.py +58 -28
  21. meerschaum/utils/misc.py +49 -16
  22. meerschaum/utils/sql.py +224 -40
  23. {meerschaum-2.5.1.dist-info → meerschaum-2.6.0.dev1.dist-info}/METADATA +1 -1
  24. {meerschaum-2.5.1.dist-info → meerschaum-2.6.0.dev1.dist-info}/RECORD +30 -30
  25. {meerschaum-2.5.1.dist-info → meerschaum-2.6.0.dev1.dist-info}/WHEEL +1 -1
  26. {meerschaum-2.5.1.dist-info → meerschaum-2.6.0.dev1.dist-info}/LICENSE +0 -0
  27. {meerschaum-2.5.1.dist-info → meerschaum-2.6.0.dev1.dist-info}/NOTICE +0 -0
  28. {meerschaum-2.5.1.dist-info → meerschaum-2.6.0.dev1.dist-info}/entry_points.txt +0 -0
  29. {meerschaum-2.5.1.dist-info → meerschaum-2.6.0.dev1.dist-info}/top_level.txt +0 -0
  30. {meerschaum-2.5.1.dist-info → meerschaum-2.6.0.dev1.dist-info}/zip-safe +0 -0
meerschaum/utils/misc.py CHANGED
@@ -214,20 +214,20 @@ def parse_config_substitution(
214
214
 
215
215
 
216
216
  def edit_file(
217
- path: Union[pathlib.Path, str],
218
- default_editor: str = 'pyvim',
219
- debug: bool = False
220
- ) -> bool:
217
+ path: Union['pathlib.Path', str],
218
+ default_editor: str = 'pyvim',
219
+ debug: bool = False
220
+ ) -> bool:
221
221
  """
222
222
  Open a file for editing.
223
-
223
+
224
224
  Attempt to launch the user's defined `$EDITOR`, otherwise use `pyvim`.
225
225
 
226
226
  Parameters
227
227
  ----------
228
228
  path: Union[pathlib.Path, str]
229
229
  The path to the file to be edited.
230
-
230
+
231
231
  default_editor: str, default 'pyvim'
232
232
  If `$EDITOR` is not set, use this instead.
233
233
  If `pyvim` is not installed, it will install it from PyPI.
@@ -250,7 +250,7 @@ def edit_file(
250
250
  rc = call([EDITOR, path])
251
251
  except Exception as e: ### can't open with default editors
252
252
  if debug:
253
- dprint(e)
253
+ dprint(str(e))
254
254
  dprint('Failed to open file with system editor. Falling back to pyvim...')
255
255
  pyvim = attempt_import('pyvim', lazy=False)
256
256
  rc = run_python_package('pyvim', [path], venv=package_venv(pyvim), debug=debug)
@@ -258,10 +258,10 @@ def edit_file(
258
258
 
259
259
 
260
260
  def is_pipe_registered(
261
- pipe: mrsm.Pipe,
262
- pipes: PipesDict,
263
- debug: bool = False
264
- ) -> bool:
261
+ pipe: mrsm.Pipe,
262
+ pipes: PipesDict,
263
+ debug: bool = False
264
+ ) -> bool:
265
265
  """
266
266
  Check if a Pipe is inside the pipes dictionary.
267
267
 
@@ -269,10 +269,10 @@ def is_pipe_registered(
269
269
  ----------
270
270
  pipe: meerschaum.Pipe
271
271
  The pipe to see if it's in the dictionary.
272
-
272
+
273
273
  pipes: PipesDict
274
274
  The dictionary to search inside.
275
-
275
+
276
276
  debug: bool, default False
277
277
  Verbosity toggle.
278
278
 
@@ -1426,7 +1426,40 @@ def flatten_list(list_: List[Any]) -> List[Any]:
1426
1426
  yield item
1427
1427
 
1428
1428
 
1429
- def make_symlink(src_path: pathlib.Path, dest_path: pathlib.Path) -> SuccessTuple:
1429
+ def parse_arguments_str(args_str: str) -> Tuple[Tuple[Any], Dict[str, Any]]:
1430
+ """
1431
+ Parse a string containing the text to be passed into a function
1432
+ and return a tuple of args, kwargs.
1433
+
1434
+ Parameters
1435
+ ----------
1436
+ args_str: str
1437
+ The contents of the function parameter (as a string).
1438
+
1439
+ Returns
1440
+ -------
1441
+ A tuple of args (tuple) and kwargs (dict[str, Any]).
1442
+
1443
+ Examples
1444
+ --------
1445
+ >>> parse_arguments_str('123, 456, foo=789, bar="baz"')
1446
+ (123, 456), {'foo': 789, 'bar': 'baz'}
1447
+ """
1448
+ import ast
1449
+ args = []
1450
+ kwargs = {}
1451
+
1452
+ for part in args_str.split(','):
1453
+ if '=' in part:
1454
+ key, val = part.split('=', 1)
1455
+ kwargs[key.strip()] = ast.literal_eval(val)
1456
+ else:
1457
+ args.append(ast.literal_eval(part.strip()))
1458
+
1459
+ return tuple(args), kwargs
1460
+
1461
+
1462
+ def make_symlink(src_path: 'pathlib.Path', dest_path: 'pathlib.Path') -> SuccessTuple:
1430
1463
  """
1431
1464
  Wrap around `pathlib.Path.symlink_to`, but add support for Windows.
1432
1465
 
@@ -1452,7 +1485,7 @@ def make_symlink(src_path: pathlib.Path, dest_path: pathlib.Path) -> SuccessTupl
1452
1485
  msg = str(e)
1453
1486
  if success:
1454
1487
  return success, "Success"
1455
-
1488
+
1456
1489
  ### Failed to create a symlink.
1457
1490
  ### If we're not on Windows, return an error.
1458
1491
  import platform
@@ -1477,7 +1510,7 @@ def make_symlink(src_path: pathlib.Path, dest_path: pathlib.Path) -> SuccessTupl
1477
1510
  shutil.copy(src_path, dest_path)
1478
1511
  except Exception as e:
1479
1512
  return False, str(e)
1480
-
1513
+
1481
1514
  return True, "Success"
1482
1515
 
1483
1516
 
meerschaum/utils/sql.py CHANGED
@@ -16,6 +16,7 @@ from meerschaum.utils.dtypes.sql import (
16
16
  PD_TO_DB_DTYPES_FLAVORS,
17
17
  get_pd_type_from_db_type as get_pd_type,
18
18
  get_db_type_from_pd_type as get_db_type,
19
+ TIMEZONE_NAIVE_FLAVORS,
19
20
  )
20
21
  from meerschaum.utils.warnings import warn
21
22
  from meerschaum.utils.debug import dprint
@@ -186,8 +187,11 @@ columns_types_queries = {
186
187
  COLUMN_NAME AS [column],
187
188
  DATA_TYPE AS [type]
188
189
  FROM {db_prefix}INFORMATION_SCHEMA.COLUMNS
189
- WHERE TABLE_NAME LIKE '{table}%'
190
- OR TABLE_NAME LIKE '{table_trunc}%'
190
+ WHERE TABLE_NAME IN (
191
+ '{table}',
192
+ '{table_trunc}'
193
+ )
194
+
191
195
  """,
192
196
  'mysql': """
193
197
  SELECT
@@ -349,9 +353,8 @@ def dateadd_str(
349
353
  "CAST('2022-01-01 00:00:00' AS TIMESTAMP) + INTERVAL '1 day'"
350
354
 
351
355
  """
352
- from meerschaum.utils.debug import dprint
353
356
  from meerschaum.utils.packages import attempt_import
354
- from meerschaum.utils.warnings import error
357
+ from meerschaum.utils.dtypes.sql import get_db_type_from_pd_type
355
358
  dateutil_parser = attempt_import('dateutil.parser')
356
359
  if 'int' in str(type(begin)).lower():
357
360
  return str(begin)
@@ -379,26 +382,32 @@ def dateadd_str(
379
382
  begin = begin.astimezone(timezone.utc)
380
383
  begin = (
381
384
  f"'{begin.replace(tzinfo=None)}'"
382
- if isinstance(begin, datetime)
385
+ if isinstance(begin, datetime) and flavor in TIMEZONE_NAIVE_FLAVORS
383
386
  else f"'{begin}'"
384
387
  )
385
388
 
389
+ dt_is_utc = begin_time.tzinfo is not None if begin_time is not None else '+' in str(begin)
390
+ db_type = get_db_type_from_pd_type(
391
+ ('datetime64[ns, UTC]' if dt_is_utc else 'datetime64[ns]'),
392
+ flavor=flavor,
393
+ )
394
+
386
395
  da = ""
387
396
  if flavor in ('postgresql', 'timescaledb', 'cockroachdb', 'citus'):
388
397
  begin = (
389
- f"CAST({begin} AS TIMESTAMP)" if begin != 'now'
390
- else "CAST(NOW() AT TIME ZONE 'utc' AS TIMESTAMP)"
398
+ f"CAST({begin} AS {db_type})" if begin != 'now'
399
+ else "CAST(NOW() AT TIME ZONE 'utc' AS {db_type})"
391
400
  )
392
401
  da = begin + (f" + INTERVAL '{number} {datepart}'" if number != 0 else '')
393
402
 
394
403
  elif flavor == 'duckdb':
395
- begin = f"CAST({begin} AS TIMESTAMP)" if begin != 'now' else 'NOW()'
404
+ begin = f"CAST({begin} AS {db_type})" if begin != 'now' else 'NOW()'
396
405
  da = begin + (f" + INTERVAL '{number} {datepart}'" if number != 0 else '')
397
406
 
398
407
  elif flavor in ('mssql',):
399
408
  if begin_time and begin_time.microsecond != 0:
400
409
  begin = begin[:-4] + "'"
401
- begin = f"CAST({begin} AS DATETIME)" if begin != 'now' else 'GETUTCDATE()'
410
+ begin = f"CAST({begin} AS {db_type})" if begin != 'now' else 'GETUTCDATE()'
402
411
  da = f"DATEADD({datepart}, {number}, {begin})" if number != 0 else begin
403
412
 
404
413
  elif flavor in ('mysql', 'mariadb'):
@@ -425,9 +434,9 @@ def dateadd_str(
425
434
 
426
435
 
427
436
  def test_connection(
428
- self,
429
- **kw: Any
430
- ) -> Union[bool, None]:
437
+ self,
438
+ **kw: Any
439
+ ) -> Union[bool, None]:
431
440
  """
432
441
  Test if a successful connection to the database may be made.
433
442
 
@@ -454,11 +463,11 @@ def test_connection(
454
463
 
455
464
 
456
465
  def get_distinct_col_count(
457
- col: str,
458
- query: str,
459
- connector: Optional[mrsm.connectors.sql.SQLConnector] = None,
460
- debug: bool = False
461
- ) -> Optional[int]:
466
+ col: str,
467
+ query: str,
468
+ connector: Optional[mrsm.connectors.sql.SQLConnector] = None,
469
+ debug: bool = False
470
+ ) -> Optional[int]:
462
471
  """
463
472
  Returns the number of distinct items in a column of a SQL query.
464
473
 
@@ -624,10 +633,10 @@ def truncate_item_name(item: str, flavor: str) -> str:
624
633
 
625
634
 
626
635
  def build_where(
627
- params: Dict[str, Any],
628
- connector: Optional[meerschaum.connectors.sql.SQLConnector] = None,
629
- with_where: bool = True,
630
- ) -> str:
636
+ params: Dict[str, Any],
637
+ connector: Optional[meerschaum.connectors.sql.SQLConnector] = None,
638
+ with_where: bool = True,
639
+ ) -> str:
631
640
  """
632
641
  Build the `WHERE` clause based on the input criteria.
633
642
 
@@ -769,7 +778,7 @@ def table_exists(
769
778
  ----------
770
779
  table: str:
771
780
  The name of the table in question.
772
-
781
+
773
782
  connector: mrsm.connectors.sql.SQLConnector
774
783
  The connector to the database which holds the table.
775
784
 
@@ -806,7 +815,7 @@ def get_sqlalchemy_table(
806
815
  ----------
807
816
  table: str
808
817
  The name of the table on the database. Does not need to be escaped.
809
-
818
+
810
819
  connector: Optional[meerschaum.connectors.sql.SQLConnector], default None:
811
820
  The connector to the database which holds the table.
812
821
 
@@ -822,7 +831,7 @@ def get_sqlalchemy_table(
822
831
 
823
832
  Returns
824
833
  -------
825
- A `sqlalchemy.Table` object for the table.
834
+ A `sqlalchemy.Table` object for the table.
826
835
 
827
836
  """
828
837
  if connector is None:
@@ -1325,35 +1334,48 @@ def get_rename_table_queries(
1325
1334
 
1326
1335
  if_exists_str = "IF EXISTS" if flavor in DROP_IF_EXISTS_FLAVORS else ""
1327
1336
  if flavor == 'duckdb':
1328
- return [
1329
- get_create_table_query(f"SELECT * FROM {old_table_name}", tmp_table, 'duckdb', schema),
1330
- get_create_table_query(f"SELECT * FROM {tmp_table_name}", new_table, 'duckdb', schema),
1331
- f"DROP TABLE {if_exists_str} {tmp_table_name}",
1332
- f"DROP TABLE {if_exists_str} {old_table_name}",
1333
- ]
1337
+ return (
1338
+ get_create_table_queries(
1339
+ f"SELECT * FROM {old_table_name}",
1340
+ tmp_table,
1341
+ 'duckdb',
1342
+ schema,
1343
+ ) + get_create_table_queries(
1344
+ f"SELECT * FROM {tmp_table_name}",
1345
+ new_table,
1346
+ 'duckdb',
1347
+ schema,
1348
+ ) + [
1349
+ f"DROP TABLE {if_exists_str} {tmp_table_name}",
1350
+ f"DROP TABLE {if_exists_str} {old_table_name}",
1351
+ ]
1352
+ )
1334
1353
 
1335
1354
  return [f"ALTER TABLE {old_table_name} RENAME TO {new_table_name}"]
1336
1355
 
1337
1356
 
1338
1357
  def get_create_table_query(
1339
- query: str,
1358
+ query_or_dtypes: Union[str, Dict[str, str]],
1340
1359
  new_table: str,
1341
1360
  flavor: str,
1342
1361
  schema: Optional[str] = None,
1343
1362
  ) -> str:
1344
1363
  """
1364
+ NOTE: This function is deprecated. Use `get_create_index_queries()` instead.
1365
+
1345
1366
  Return a query to create a new table from a `SELECT` query.
1346
1367
 
1347
1368
  Parameters
1348
1369
  ----------
1349
- query: str
1370
+ query: Union[str, Dict[str, str]]
1350
1371
  The select query to use for the creation of the table.
1372
+ If a dictionary is provided, return a `CREATE TABLE` query from the given `dtypes` columns.
1351
1373
 
1352
1374
  new_table: str
1353
1375
  The unquoted name of the new table.
1354
1376
 
1355
1377
  flavor: str
1356
- The database flavor to use for the query (e.g. `'mssql'`, `'postgresql'`.
1378
+ The database flavor to use for the query (e.g. `'mssql'`, `'postgresql'`).
1357
1379
 
1358
1380
  schema: Optional[str], default None
1359
1381
  The schema on which the table will reside.
@@ -1362,26 +1384,164 @@ def get_create_table_query(
1362
1384
  -------
1363
1385
  A `CREATE TABLE` (or `SELECT INTO`) query for the database flavor.
1364
1386
  """
1387
+ return get_create_table_queries(
1388
+ query_or_dtypes,
1389
+ new_table,
1390
+ flavor,
1391
+ schema=schema,
1392
+ primary_key=None,
1393
+ )[0]
1394
+
1395
+
1396
+ def get_create_table_queries(
1397
+ query_or_dtypes: Union[str, Dict[str, str]],
1398
+ new_table: str,
1399
+ flavor: str,
1400
+ schema: Optional[str] = None,
1401
+ primary_key: Optional[str] = None,
1402
+ ) -> List[str]:
1403
+ """
1404
+ Return a query to create a new table from a `SELECT` query or a `dtypes` dictionary.
1405
+
1406
+ Parameters
1407
+ ----------
1408
+ query_or_dtypes: Union[str, Dict[str, str]]
1409
+ The select query to use for the creation of the table.
1410
+ If a dictionary is provided, return a `CREATE TABLE` query from the given `dtypes` columns.
1411
+
1412
+ new_table: str
1413
+ The unquoted name of the new table.
1414
+
1415
+ flavor: str
1416
+ The database flavor to use for the query (e.g. `'mssql'`, `'postgresql'`).
1417
+
1418
+ schema: Optional[str], default None
1419
+ The schema on which the table will reside.
1420
+
1421
+ primary_key: Optional[str], default None
1422
+ If provided, designate this column as the primary key in the new table.
1423
+
1424
+ Returns
1425
+ -------
1426
+ A `CREATE TABLE` (or `SELECT INTO`) query for the database flavor.
1427
+ """
1428
+ if not isinstance(query_or_dtypes, (str, dict)):
1429
+ raise TypeError("`query_or_dtypes` must be a query or a dtypes dictionary.")
1430
+
1431
+ method = (
1432
+ _get_create_table_query_from_cte
1433
+ if isinstance(query_or_dtypes, str)
1434
+ else _get_create_table_query_from_dtypes
1435
+ )
1436
+ return method(
1437
+ query_or_dtypes,
1438
+ new_table,
1439
+ flavor,
1440
+ schema=schema,
1441
+ primary_key=primary_key,
1442
+ )
1443
+
1444
+
1445
+ def _get_create_table_query_from_dtypes(
1446
+ dtypes: Dict[str, str],
1447
+ new_table: str,
1448
+ flavor: str,
1449
+ schema: Optional[str] = None,
1450
+ primary_key: Optional[str] = None,
1451
+ ) -> List[str]:
1452
+ """
1453
+ Create a new table from a `dtypes` dictionary.
1454
+ """
1455
+ from meerschaum.utils.dtypes.sql import get_db_type_from_pd_type, AUTO_INCREMENT_COLUMN_FLAVORS
1456
+ if not dtypes and not primary_key:
1457
+ raise ValueError(f"Expecting columns for table '{new_table}'.")
1458
+
1459
+ cols_types = (
1460
+ [(primary_key, get_db_type_from_pd_type(dtypes.get(primary_key, 'int')))]
1461
+ if primary_key
1462
+ else []
1463
+ ) + [
1464
+ (col, get_db_type_from_pd_type(typ))
1465
+ for col, typ in dtypes.items()
1466
+ if col != primary_key
1467
+ ]
1468
+
1469
+ table_name = sql_item_name(new_table, schema=schema, flavor=flavor)
1470
+ query = f"CREATE TABLE {table_name} ("
1471
+ if primary_key:
1472
+ col_db_type = cols_types[0][1]
1473
+ auto_increment = (' ' + AUTO_INCREMENT_COLUMN_FLAVORS.get(
1474
+ flavor,
1475
+ AUTO_INCREMENT_COLUMN_FLAVORS['default']
1476
+ )) if primary_key not in dtypes else ''
1477
+ col_name = sql_item_name(primary_key, flavor=flavor, schema=None)
1478
+
1479
+ if flavor == 'sqlite':
1480
+ query += f"\n {col_name} INTEGER PRIMARY KEY{auto_increment} NOT NULL,"
1481
+ else:
1482
+ query += f"\n {col_name} {col_db_type} PRIMARY KEY{auto_increment} NOT NULL,"
1483
+
1484
+ for col, db_type in cols_types:
1485
+ if col == primary_key:
1486
+ continue
1487
+ col_name = sql_item_name(col, schema=None, flavor=flavor)
1488
+ query += f"\n {col_name} {db_type},"
1489
+ query = query[:-1]
1490
+ query += "\n)"
1491
+
1492
+ return [query]
1493
+
1494
+
1495
+ def _get_create_table_query_from_cte(
1496
+ query: str,
1497
+ new_table: str,
1498
+ flavor: str,
1499
+ schema: Optional[str] = None,
1500
+ primary_key: Optional[str] = None,
1501
+ ) -> List[str]:
1502
+ """
1503
+ Create a new table from a CTE query.
1504
+ """
1365
1505
  import textwrap
1506
+ from meerschaum.utils.dtypes.sql import AUTO_INCREMENT_COLUMN_FLAVORS
1366
1507
  create_cte = 'create_query'
1367
1508
  create_cte_name = sql_item_name(create_cte, flavor, None)
1368
1509
  new_table_name = sql_item_name(new_table, flavor, schema)
1510
+ primary_key_constraint_name = (
1511
+ sql_item_name(f'pk_{new_table}', flavor, None)
1512
+ if primary_key
1513
+ else None
1514
+ )
1515
+ primary_key_name = (
1516
+ sql_item_name(primary_key, flavor, None)
1517
+ if primary_key
1518
+ else None
1519
+ )
1520
+ auto_increment = AUTO_INCREMENT_COLUMN_FLAVORS.get(
1521
+ flavor,
1522
+ AUTO_INCREMENT_COLUMN_FLAVORS['default']
1523
+ )
1369
1524
  if flavor in ('mssql',):
1370
1525
  query = query.lstrip()
1371
1526
  if 'with ' in query.lower():
1372
1527
  final_select_ix = query.lower().rfind('select')
1373
- return (
1528
+ create_table_query = (
1374
1529
  query[:final_select_ix].rstrip() + ',\n'
1375
1530
  + f"{create_cte_name} AS (\n"
1376
1531
  + query[final_select_ix:]
1377
1532
  + "\n)\n"
1378
1533
  + f"SELECT *\nINTO {new_table_name}\nFROM {create_cte_name}"
1379
1534
  )
1380
-
1381
- create_table_query = f"""
1382
- SELECT *
1383
- INTO {new_table_name}
1384
- FROM ({query}) AS {create_cte_name}
1535
+ else:
1536
+ create_table_query = f"""
1537
+ SELECT *
1538
+ INTO {new_table_name}
1539
+ FROM ({query}) AS {create_cte_name}
1540
+ """
1541
+
1542
+ alter_type_query = f"""
1543
+ ALTER TABLE {new_table_name}
1544
+ ADD CONSTRAINT {primary_key_constraint_name} PRIMARY KEY ({primary_key_name})
1385
1545
  """
1386
1546
  elif flavor in (None,):
1387
1547
  create_table_query = f"""
@@ -1390,12 +1550,22 @@ def get_create_table_query(
1390
1550
  SELECT *
1391
1551
  FROM {create_cte_name}
1392
1552
  """
1553
+
1554
+ alter_type_query = f"""
1555
+ ALTER TABLE {new_table_name}
1556
+ ADD PRIMARY KEY ({primary_key_name})
1557
+ """
1393
1558
  elif flavor in ('sqlite', 'mysql', 'mariadb', 'duckdb', 'oracle'):
1394
1559
  create_table_query = f"""
1395
1560
  CREATE TABLE {new_table_name} AS
1396
1561
  SELECT *
1397
1562
  FROM ({query})""" + (f""" AS {create_cte_name}""" if flavor != 'oracle' else '') + """
1398
1563
  """
1564
+
1565
+ alter_type_query = f"""
1566
+ ALTER TABLE {new_table_name}
1567
+ ADD PRIMARY KEY ({primary_key_name})
1568
+ """
1399
1569
  else:
1400
1570
  create_table_query = f"""
1401
1571
  SELECT *
@@ -1403,7 +1573,21 @@ def get_create_table_query(
1403
1573
  FROM ({query}) AS {create_cte_name}
1404
1574
  """
1405
1575
 
1406
- return textwrap.dedent(create_table_query)
1576
+ alter_type_query = f"""
1577
+ ALTER TABLE {new_table_name}
1578
+ ADD PRIMARY KEY ({primary_key_name})
1579
+ """
1580
+
1581
+ create_table_query = textwrap.dedent(create_table_query)
1582
+ if not primary_key:
1583
+ return [create_table_query]
1584
+
1585
+ alter_type_query = textwrap.dedent(alter_type_query)
1586
+
1587
+ return [
1588
+ create_table_query,
1589
+ alter_type_query,
1590
+ ]
1407
1591
 
1408
1592
 
1409
1593
  def wrap_query_with_cte(
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: meerschaum
3
- Version: 2.5.1
3
+ Version: 2.6.0.dev1
4
4
  Summary: Sync Time-Series Pipes with Meerschaum
5
5
  Home-page: https://meerschaum.io
6
6
  Author: Bennett Meares