singlestoredb 1.7.2__tar.gz → 1.8.0__tar.gz

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.

Potentially problematic release.


This version of singlestoredb might be problematic. Click here for more details.

Files changed (141) hide show
  1. {singlestoredb-1.7.2/singlestoredb.egg-info → singlestoredb-1.8.0}/PKG-INFO +1 -1
  2. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/setup.cfg +3 -1
  3. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/__init__.py +1 -1
  4. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/fusion/handler.py +68 -18
  5. singlestoredb-1.8.0/singlestoredb/fusion/handlers/export.py +245 -0
  6. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/fusion/handlers/job.py +4 -20
  7. singlestoredb-1.8.0/singlestoredb/management/export.py +310 -0
  8. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/test_fusion.py +0 -4
  9. singlestoredb-1.8.0/singlestoredb/utils/__init__.py +0 -0
  10. {singlestoredb-1.7.2 → singlestoredb-1.8.0/singlestoredb.egg-info}/PKG-INFO +1 -1
  11. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb.egg-info/SOURCES.txt +3 -0
  12. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/LICENSE +0 -0
  13. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/README.md +0 -0
  14. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/accel.c +0 -0
  15. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/setup.py +0 -0
  16. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/ai/__init__.py +0 -0
  17. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/ai/embeddings.py +0 -0
  18. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/alchemy/__init__.py +0 -0
  19. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/apps/__init__.py +0 -0
  20. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/apps/_cloud_functions.py +0 -0
  21. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/apps/_config.py +0 -0
  22. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/apps/_connection_info.py +0 -0
  23. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/apps/_dashboards.py +0 -0
  24. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/apps/_process.py +0 -0
  25. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/apps/_stdout_supress.py +0 -0
  26. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/apps/_uvicorn_util.py +0 -0
  27. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/auth.py +0 -0
  28. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/config.py +0 -0
  29. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/connection.py +0 -0
  30. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/converters.py +0 -0
  31. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/exceptions.py +0 -0
  32. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/functions/__init__.py +0 -0
  33. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/functions/decorator.py +0 -0
  34. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/functions/dtypes.py +0 -0
  35. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/functions/ext/__init__.py +0 -0
  36. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/functions/ext/arrow.py +0 -0
  37. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/functions/ext/asgi.py +0 -0
  38. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/functions/ext/json.py +0 -0
  39. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/functions/ext/mmap.py +0 -0
  40. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/functions/ext/rowdat_1.py +0 -0
  41. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/functions/ext/utils.py +0 -0
  42. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/functions/signature.py +0 -0
  43. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/fusion/__init__.py +0 -0
  44. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/fusion/graphql.py +0 -0
  45. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/fusion/handlers/__init__.py +0 -0
  46. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/fusion/handlers/stage.py +0 -0
  47. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/fusion/handlers/utils.py +0 -0
  48. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/fusion/handlers/workspace.py +0 -0
  49. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/fusion/registry.py +0 -0
  50. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/fusion/result.py +0 -0
  51. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/http/__init__.py +0 -0
  52. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/http/connection.py +0 -0
  53. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/management/__init__.py +0 -0
  54. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/management/billing_usage.py +0 -0
  55. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/management/cluster.py +0 -0
  56. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/management/job.py +0 -0
  57. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/management/manager.py +0 -0
  58. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/management/organization.py +0 -0
  59. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/management/region.py +0 -0
  60. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/management/utils.py +0 -0
  61. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/management/workspace.py +0 -0
  62. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/__init__.py +0 -0
  63. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/_auth.py +0 -0
  64. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/charset.py +0 -0
  65. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/connection.py +0 -0
  66. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/constants/CLIENT.py +0 -0
  67. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/constants/COMMAND.py +0 -0
  68. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/constants/CR.py +0 -0
  69. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/constants/ER.py +0 -0
  70. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/constants/EXTENDED_TYPE.py +0 -0
  71. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/constants/FIELD_TYPE.py +0 -0
  72. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/constants/FLAG.py +0 -0
  73. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/constants/SERVER_STATUS.py +0 -0
  74. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/constants/VECTOR_TYPE.py +0 -0
  75. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/constants/__init__.py +0 -0
  76. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/converters.py +0 -0
  77. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/cursors.py +0 -0
  78. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/err.py +0 -0
  79. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/optionfile.py +0 -0
  80. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/protocol.py +0 -0
  81. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/__init__.py +0 -0
  82. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/base.py +0 -0
  83. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/conftest.py +0 -0
  84. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/test_DictCursor.py +0 -0
  85. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/test_SSCursor.py +0 -0
  86. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/test_basic.py +0 -0
  87. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/test_connection.py +0 -0
  88. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/test_converters.py +0 -0
  89. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/test_cursor.py +0 -0
  90. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/test_err.py +0 -0
  91. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/test_issues.py +0 -0
  92. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/test_load_local.py +0 -0
  93. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/test_nextset.py +0 -0
  94. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/test_optionfile.py +0 -0
  95. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/thirdparty/__init__.py +0 -0
  96. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +0 -0
  97. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/thirdparty/test_MySQLdb/capabilities.py +0 -0
  98. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/thirdparty/test_MySQLdb/dbapi20.py +0 -0
  99. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +0 -0
  100. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +0 -0
  101. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +0 -0
  102. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/mysql/times.py +0 -0
  103. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/notebook/__init__.py +0 -0
  104. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/notebook/_objects.py +0 -0
  105. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/notebook/_portal.py +0 -0
  106. /singlestoredb-1.7.2/singlestoredb/tests/__init__.py → /singlestoredb-1.8.0/singlestoredb/py.typed +0 -0
  107. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/pytest.py +0 -0
  108. {singlestoredb-1.7.2/singlestoredb/utils → singlestoredb-1.8.0/singlestoredb/tests}/__init__.py +0 -0
  109. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/empty.sql +0 -0
  110. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/ext_funcs/__init__.py +0 -0
  111. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/local_infile.csv +0 -0
  112. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/test.sql +0 -0
  113. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/test2.sql +0 -0
  114. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/test_basics.py +0 -0
  115. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/test_config.py +0 -0
  116. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/test_connection.py +0 -0
  117. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/test_dbapi.py +0 -0
  118. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/test_exceptions.py +0 -0
  119. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/test_ext_func.py +0 -0
  120. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/test_ext_func_data.py +0 -0
  121. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/test_http.py +0 -0
  122. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/test_management.py +0 -0
  123. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/test_plugin.py +0 -0
  124. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/test_results.py +0 -0
  125. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/test_types.py +0 -0
  126. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/test_udf.py +0 -0
  127. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/test_xdict.py +0 -0
  128. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/tests/utils.py +0 -0
  129. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/types.py +0 -0
  130. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/utils/config.py +0 -0
  131. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/utils/convert_rows.py +0 -0
  132. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/utils/debug.py +0 -0
  133. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/utils/dtypes.py +0 -0
  134. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/utils/events.py +0 -0
  135. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/utils/mogrify.py +0 -0
  136. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/utils/results.py +0 -0
  137. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb/utils/xdict.py +0 -0
  138. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb.egg-info/dependency_links.txt +0 -0
  139. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb.egg-info/entry_points.txt +0 -0
  140. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb.egg-info/requires.txt +0 -0
  141. {singlestoredb-1.7.2 → singlestoredb-1.8.0}/singlestoredb.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: singlestoredb
3
- Version: 1.7.2
3
+ Version: 1.8.0
4
4
  Summary: Interface to the SingleStoreDB database and workspace management APIs
5
5
  Home-page: https://github.com/singlestore-labs/singlestoredb-python
6
6
  Author: SingleStore
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = singlestoredb
3
- version = 1.7.2
3
+ version = 1.8.0
4
4
  description = Interface to the SingleStoreDB database and workspace management APIs
5
5
  long_description = file: README.md
6
6
  long_description_content_type = text/markdown
@@ -28,6 +28,7 @@ install_requires =
28
28
  wheel
29
29
  tomli>=1.1.0;python_version < '3.11'
30
30
  python_requires = >=3.8
31
+ include_package_data = True
31
32
  tests_require =
32
33
  coverage
33
34
  pytest
@@ -66,6 +67,7 @@ sqlalchemy =
66
67
 
67
68
  [options.package_data]
68
69
  * =
70
+ *.typed
69
71
  *.sql
70
72
  *.csv
71
73
 
@@ -13,7 +13,7 @@ Examples
13
13
 
14
14
  """
15
15
 
16
- __version__ = '1.7.2'
16
+ __version__ = '1.8.0'
17
17
 
18
18
  from typing import Any
19
19
 
@@ -11,6 +11,7 @@ from typing import Dict
11
11
  from typing import Iterable
12
12
  from typing import List
13
13
  from typing import Optional
14
+ from typing import Set
14
15
  from typing import Tuple
15
16
 
16
17
  from parsimonious import Grammar
@@ -23,9 +24,9 @@ from ..connection import Connection
23
24
 
24
25
  CORE_GRAMMAR = r'''
25
26
  ws = ~r"(\s+|(\s*/\*.*\*/\s*)+)"
26
- qs = ~r"\"([^\"]*)\"|'([^\']*)'|`([^\`]*)`|([A-Za-z0-9_\-\.]+)"
27
- number = ~r"[-+]?(\d*\.)?\d+(e[-+]?\d+)?"i
28
- integer = ~r"-?\d+"
27
+ qs = ~r"\"([^\"]*)\"|'([^\']*)'|([A-Za-z0-9_\-\.]+)|`([^\`]+)`" ws*
28
+ number = ~r"[-+]?(\d*\.)?\d+(e[-+]?\d+)?"i ws*
29
+ integer = ~r"-?\d+" ws*
29
30
  comma = ws* "," ws*
30
31
  eq = ws* "=" ws*
31
32
  open_paren = ws* "(" ws*
@@ -33,6 +34,10 @@ CORE_GRAMMAR = r'''
33
34
  open_repeats = ws* ~r"[\(\[\{]" ws*
34
35
  close_repeats = ws* ~r"[\)\]\}]" ws*
35
36
  select = ~r"SELECT"i ws+ ~r".+" ws*
37
+ table = ~r"(?:([A-Za-z0-9_\-]+)|`([^\`]+)`)(?:\.(?:([A-Za-z0-9_\-]+)|`([^\`]+)`))?" ws*
38
+ column = ~r"(?:([A-Za-z0-9_\-]+)|`([^\`]+)`)(?:\.(?:([A-Za-z0-9_\-]+)|`([^\`]+)`))?" ws*
39
+ link_name = ~r"(?:([A-Za-z0-9_\-]+)|`([^\`]+)`)(?:\.(?:([A-Za-z0-9_\-]+)|`([^\`]+)`))?" ws*
40
+ catalog_name = ~r"(?:([A-Za-z0-9_\-]+)|`([^\`]+)`)(?:\.(?:([A-Za-z0-9_\-]+)|`([^\`]+)`))?" ws*
36
41
 
37
42
  json = ws* json_object ws*
38
43
  json_object = ~r"{\s*" json_members? ~r"\s*}"
@@ -65,6 +70,10 @@ BUILTINS = {
65
70
  '<integer>': '',
66
71
  '<number>': '',
67
72
  '<json>': '',
73
+ '<table>': '',
74
+ '<column>': '',
75
+ '<catalog-name>': '',
76
+ '<link-name>': '',
68
77
  }
69
78
 
70
79
  BUILTIN_DEFAULTS = { # type: ignore
@@ -226,9 +235,13 @@ def build_syntax(grammar: str) -> str:
226
235
  # Split on ';' on a line by itself
227
236
  cmd, end = grammar.split(';', 1)
228
237
 
229
- rules = {}
238
+ name = ''
239
+ rules: Dict[str, Any] = {}
230
240
  for line in end.split('\n'):
231
241
  line = line.strip()
242
+ if line.startswith('&'):
243
+ rules[name] += '\n' + line
244
+ continue
232
245
  if not line:
233
246
  continue
234
247
  name, value = line.split('=', 1)
@@ -239,10 +252,16 @@ def build_syntax(grammar: str) -> str:
239
252
  while re.search(r' [a-z0-9_]+\b', cmd):
240
253
  cmd = re.sub(r' ([a-z0-9_]+)\b', functools.partial(expand_rules, rules), cmd)
241
254
 
255
+ def add_indent(m: Any) -> str:
256
+ return ' ' + (len(m.group(1)) * ' ')
257
+
258
+ # Indent line-continuations
259
+ cmd = re.sub(r'^(\&+)\s*', add_indent, cmd, flags=re.M)
260
+
242
261
  cmd = textwrap.dedent(cmd).rstrip() + ';'
243
- cmd = re.sub(r' +', ' ', cmd)
244
- cmd = re.sub(r'^ ', ' ', cmd, flags=re.M)
245
- cmd = re.sub(r'\s+,\.\.\.', ',...', cmd)
262
+ cmd = re.sub(r'(\S) +', r'\1 ', cmd)
263
+ cmd = re.sub(r'<comma>', ',', cmd)
264
+ cmd = re.sub(r'\s+,\s*\.\.\.', ',...', cmd)
246
265
 
247
266
  return cmd
248
267
 
@@ -399,9 +418,15 @@ def process_grammar(
399
418
  help_txt = build_help(syntax_txt, full_grammar)
400
419
  grammar = build_cmd(grammar)
401
420
 
421
+ # Remove line-continuations
422
+ grammar = re.sub(r'\n\s*&+', r'', grammar)
423
+
402
424
  # Make sure grouping characters all have whitespace around them
403
425
  grammar = re.sub(r' *(\[|\{|\||\}|\]) *', r' \1 ', grammar)
404
426
 
427
+ grammar = re.sub(r'\(', r' open_paren ', grammar)
428
+ grammar = re.sub(r'\)', r' close_paren ', grammar)
429
+
405
430
  for line in grammar.split('\n'):
406
431
  if not line.strip():
407
432
  continue
@@ -418,7 +443,7 @@ def process_grammar(
418
443
  sql = re.sub(r'\]\s+\[', r' | ', sql)
419
444
 
420
445
  # Lower-case keywords and make them case-insensitive
421
- sql = re.sub(r'(\b|@+)([A-Z0-9]+)\b', lower_and_regex, sql)
446
+ sql = re.sub(r'(\b|@+)([A-Z0-9_]+)\b', lower_and_regex, sql)
422
447
 
423
448
  # Convert literal strings to 'qs'
424
449
  sql = re.sub(r"'[^']+'", r'qs', sql)
@@ -461,12 +486,18 @@ def process_grammar(
461
486
  sql = re.sub(r'\s+ws$', r' ws*', sql)
462
487
  sql = re.sub(r'\s+ws\s+\(', r' ws* (', sql)
463
488
  sql = re.sub(r'\)\s+ws\s+', r') ws* ', sql)
464
- sql = re.sub(r'\s+ws\s+', r' ws+ ', sql)
489
+ sql = re.sub(r'\s+ws\s+', r' ws* ', sql)
465
490
  sql = re.sub(r'\?\s+ws\+', r'? ws*', sql)
466
491
 
467
492
  # Remove extra ws around eq
468
493
  sql = re.sub(r'ws\+\s*eq\b', r'eq', sql)
469
494
 
495
+ # Remove optional groupings when mandatory groupings are specified
496
+ sql = re.sub(r'open_paren\s+ws\*\s+open_repeats\?', r'open_paren', sql)
497
+ sql = re.sub(r'close_repeats\?\s+ws\*\s+close_paren', r'close_paren', sql)
498
+ sql = re.sub(r'open_paren\s+open_repeats\?', r'open_paren', sql)
499
+ sql = re.sub(r'close_repeats\?\s+close_paren', r'close_paren', sql)
500
+
470
501
  out.append(f'{op} = {sql}')
471
502
 
472
503
  for k, v in list(rules.items()):
@@ -548,6 +579,7 @@ class SQLHandler(NodeVisitor):
548
579
 
549
580
  def __init__(self, connection: Connection):
550
581
  self.connection = connection
582
+ self._handled: Set[str] = set()
551
583
 
552
584
  @classmethod
553
585
  def compile(cls, grammar: str = '') -> None:
@@ -614,12 +646,16 @@ class SQLHandler(NodeVisitor):
614
646
  )
615
647
 
616
648
  type(self).compile()
649
+ self._handled = set()
617
650
  try:
618
651
  params = self.visit(type(self).grammar.parse(sql))
619
652
  for k, v in params.items():
620
653
  params[k] = self.validate_rule(k, v)
621
654
 
622
655
  res = self.run(params)
656
+
657
+ self._handled = set()
658
+
623
659
  if res is not None:
624
660
  res.format_results(self.connection)
625
661
  return res
@@ -666,16 +702,20 @@ class SQLHandler(NodeVisitor):
666
702
  """Quoted strings."""
667
703
  if node is None:
668
704
  return None
669
- return node.match.group(1) or node.match.group(2) or \
670
- node.match.group(3) or node.match.group(4)
705
+ return flatten(visited_children)[0]
706
+
707
+ def visit_compound(self, node: Node, visited_children: Iterable[Any]) -> Any:
708
+ """Compound name."""
709
+ print(visited_children)
710
+ return flatten(visited_children)[0]
671
711
 
672
712
  def visit_number(self, node: Node, visited_children: Iterable[Any]) -> Any:
673
713
  """Numeric value."""
674
- return float(node.match.group(0))
714
+ return float(flatten(visited_children)[0])
675
715
 
676
716
  def visit_integer(self, node: Node, visited_children: Iterable[Any]) -> Any:
677
717
  """Integer value."""
678
- return int(node.match.group(0))
718
+ return int(flatten(visited_children)[0])
679
719
 
680
720
  def visit_ws(self, node: Node, visited_children: Iterable[Any]) -> Any:
681
721
  """Whitespace and comments."""
@@ -804,19 +844,29 @@ class SQLHandler(NodeVisitor):
804
844
  if node.expr_name.endswith('_cmd'):
805
845
  final = merge_dicts(flatten(visited_children)[n_keywords:])
806
846
  for k, v in type(self).rule_info.items():
807
- if k.endswith('_cmd') or k.endswith('_'):
847
+ if k.endswith('_cmd') or k.endswith('_') or k.startswith('_'):
808
848
  continue
809
- if k not in final:
849
+ if k not in final and k not in self._handled:
810
850
  final[k] = BUILTIN_DEFAULTS.get(k, v['default'])
811
851
  return final
812
852
 
813
853
  # Filter out stray empty strings
814
854
  out = [x for x in flatten(visited_children)[n_keywords:] if x]
815
855
 
816
- if repeats or len(out) > 1:
817
- return {node.expr_name: out}
856
+ # Remove underscore prefixes from rule name
857
+ key_name = re.sub(r'^_+', r'', node.expr_name)
818
858
 
819
- return {node.expr_name: out[0] if out else True}
859
+ if repeats or len(out) > 1:
860
+ self._handled.add(node.expr_name)
861
+ # If all outputs are dicts, merge them
862
+ if len(out) > 1 and not repeats:
863
+ is_dicts = [x for x in out if isinstance(x, dict)]
864
+ if len(is_dicts) == len(out):
865
+ return {key_name: merge_dicts(out)}
866
+ return {key_name: out}
867
+
868
+ self._handled.add(node.expr_name)
869
+ return {key_name: out[0] if out else True}
820
870
 
821
871
  if hasattr(node, 'match'):
822
872
  if not visited_children and not node.match.groups():
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env python3
2
+ import json
3
+ from typing import Any
4
+ from typing import Dict
5
+ from typing import Optional
6
+
7
+ from .. import result
8
+ from ...management.export import Catalog
9
+ from ...management.export import ExportService
10
+ from ...management.export import ExportStatus
11
+ from ...management.export import Link
12
+ from ..handler import SQLHandler
13
+ from ..result import FusionSQLResult
14
+ from .utils import get_workspace_group
15
+
16
+
17
+ class CreateClusterIdentity(SQLHandler):
18
+ """
19
+ CREATE CLUSTER IDENTITY
20
+ catalog
21
+ storage
22
+ ;
23
+
24
+ # Catolog
25
+ catalog = CATALOG { _catalog_config | _catalog_creds }
26
+ _catalog_config = CONFIG '<catalog-config>'
27
+ _catalog_creds = CREDENTIALS '<catalog-creds>'
28
+
29
+ # Storage
30
+ storage = LINK { _link_config | _link_creds }
31
+ _link_config = S3 CONFIG '<link-config>'
32
+ _link_creds = CREDENTIALS '<link-creds>'
33
+
34
+ Description
35
+ -----------
36
+ Create a cluster identity for allowing the export service to access
37
+ external cloud resources.
38
+
39
+ Arguments
40
+ ---------
41
+ * ``<catalog-config>`` and ``<catalog-creds>``: Catalog configuration
42
+ and credentials in JSON format.
43
+ * ``<link-config>`` and ``<link-creds>``: Storage link configuration
44
+ and credentials in JSON format.
45
+
46
+ Remarks
47
+ -------
48
+ * ``FROM <table>`` specifies the SingleStore table to export. The same name will
49
+ be used for the exported table.
50
+ * ``CATALOG`` specifies the details of the catalog to connect to.
51
+ * ``LINK`` specifies the details of the data storage to connect to.
52
+
53
+ Example
54
+ -------
55
+ The following statement creates a cluster identity for the catalog
56
+ and link::
57
+
58
+ CREATE CLUSTER IDENTITY
59
+ CATALOG CONFIG '{
60
+ "type": "GLUE",
61
+ "table_format": "ICEBERG",
62
+ "id": "13983498723498",
63
+ "region": "us-east-1"
64
+ }'
65
+ LINK S3 CONFIG '{
66
+ "region": "us-east-1",
67
+ "endpoint_url": "s3://bucket-name"
68
+
69
+ }'
70
+ ;
71
+
72
+ """
73
+
74
+ _enabled = False
75
+
76
+ def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
77
+ # Catalog
78
+ catalog_config = json.loads(params['catalog'].get('catalog_config', '{}') or '{}')
79
+ catalog_creds = json.loads(params['catalog'].get('catalog_creds', '{}') or '{}')
80
+
81
+ # Storage
82
+ storage_config = json.loads(params['storage'].get('link_config', '{}') or '{}')
83
+ storage_creds = json.loads(params['storage'].get('link_creds', '{}') or '{}')
84
+
85
+ wsg = get_workspace_group({})
86
+
87
+ if wsg._manager is None:
88
+ raise TypeError('no workspace manager is associated with workspace group')
89
+
90
+ out = ExportService(
91
+ wsg,
92
+ 'none',
93
+ 'none',
94
+ Catalog.from_config_and_creds(catalog_config, catalog_creds, wsg._manager),
95
+ Link.from_config_and_creds('S3', storage_config, storage_creds, wsg._manager),
96
+ columns=None,
97
+ ).create_cluster_identity()
98
+
99
+ res = FusionSQLResult()
100
+ res.add_field('RoleARN', result.STRING)
101
+ res.set_rows([(out['roleARN'],)])
102
+
103
+ return res
104
+
105
+
106
+ CreateClusterIdentity.register(overwrite=True)
107
+
108
+
109
+ class CreateExport(SQLHandler):
110
+ """
111
+ START EXPORT
112
+ from_table
113
+ catalog
114
+ storage
115
+ ;
116
+
117
+ # From table
118
+ from_table = FROM <table>
119
+
120
+ # Catolog
121
+ catalog = CATALOG [ _catalog_config ] [ _catalog_creds ]
122
+ _catalog_config = CONFIG '<catalog-config>'
123
+ _catalog_creds = CREDENTIALS '<catalog-creds>'
124
+
125
+ # Storage
126
+ storage = LINK [ _link_config ] [ _link_creds ]
127
+ _link_config = S3 CONFIG '<link-config>'
128
+ _link_creds = CREDENTIALS '<link-creds>'
129
+
130
+ Description
131
+ -----------
132
+ Create an export configuration.
133
+
134
+ Arguments
135
+ ---------
136
+ * ``<catalog-config>`` and ``<catalog-creds>``: The catalog configuration.
137
+ * ``<link-config>`` and ``<link-creds>``: The storage link configuration.
138
+
139
+ Remarks
140
+ -------
141
+ * ``FROM <table>`` specifies the SingleStore table to export. The same name will
142
+ be used for the exported table.
143
+ * ``CATALOG`` specifies the details of the catalog to connect to.
144
+ * ``LINK`` specifies the details of the data storage to connect to.
145
+
146
+ Examples
147
+ --------
148
+ The following statement starts an export operation with the given
149
+ catalog and link configurations. The source table to export is
150
+ named "customer_data"::
151
+
152
+ START EXPORT FROM customer_data
153
+ CATALOG CONFIG '{
154
+ "type": "GLUE",
155
+ "table_format": "ICEBERG",
156
+ "id": "13983498723498",
157
+ "region": "us-east-1"
158
+ }'
159
+ LINK S3 CONFIG '{
160
+ "region": "us-east-1",
161
+ "endpoint_url": "s3://bucket-name"
162
+
163
+ }'
164
+ ;
165
+
166
+ """ # noqa
167
+
168
+ _enabled = False
169
+
170
+ def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
171
+ # From table
172
+ if isinstance(params['from_table'], str):
173
+ from_database = None
174
+ from_table = params['from_table']
175
+ else:
176
+ from_database, from_table = params['from_table']
177
+
178
+ # Catalog
179
+ catalog_config = json.loads(params['catalog'].get('catalog_config', '{}') or '{}')
180
+ catalog_creds = json.loads(params['catalog'].get('catalog_creds', '{}') or '{}')
181
+
182
+ # Storage
183
+ storage_config = json.loads(params['storage'].get('link_config', '{}') or '{}')
184
+ storage_creds = json.loads(params['storage'].get('link_creds', '{}') or '{}')
185
+
186
+ wsg = get_workspace_group({})
187
+
188
+ if from_database is None:
189
+ raise ValueError('database name must be specified for source table')
190
+
191
+ if wsg._manager is None:
192
+ raise TypeError('no workspace manager is associated with workspace group')
193
+
194
+ out = ExportService(
195
+ wsg,
196
+ from_database,
197
+ from_table,
198
+ Catalog.from_config_and_creds(catalog_config, catalog_creds, wsg._manager),
199
+ Link.from_config_and_creds('S3', storage_config, storage_creds, wsg._manager),
200
+ columns=None,
201
+ ).start()
202
+
203
+ res = FusionSQLResult()
204
+ res.add_field('ExportID', result.STRING)
205
+ res.set_rows([(out.export_id,)])
206
+
207
+ return res
208
+
209
+
210
+ CreateExport.register(overwrite=True)
211
+
212
+
213
+ class ShowExport(SQLHandler):
214
+ """
215
+ SHOW EXPORT export_id;
216
+
217
+ # ID of export
218
+ export_id = '<export-id>'
219
+
220
+ """
221
+
222
+ _enabled = False
223
+
224
+ def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
225
+ wsg = get_workspace_group({})
226
+ out = ExportStatus(params['export_id'], wsg)
227
+
228
+ status = out._info()
229
+
230
+ res = FusionSQLResult()
231
+ res.add_field('ExportID', result.STRING)
232
+ res.add_field('Status', result.STRING)
233
+ res.add_field('Message', result.STRING)
234
+ res.set_rows([
235
+ (
236
+ params['export_id'],
237
+ status.get('status', 'Unknown'),
238
+ status.get('statusMsg', ''),
239
+ ),
240
+ ])
241
+
242
+ return res
243
+
244
+
245
+ ShowExport.register(overwrite=True)
@@ -122,8 +122,6 @@ class ScheduleJobHandler(SQLHandler):
122
122
  ;
123
123
  """
124
124
 
125
- _enabled = False
126
-
127
125
  def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
128
126
  res = FusionSQLResult()
129
127
  res.add_field('JobID', result.STRING)
@@ -138,8 +136,8 @@ class ScheduleJobHandler(SQLHandler):
138
136
 
139
137
  execution_interval_in_mins = None
140
138
  if params.get('execute_every'):
141
- execution_interval_in_mins = params['execute_every'][0]['interval']
142
- time_unit = params['execute_every'][-1]['time_unit'].upper()
139
+ execution_interval_in_mins = params['execute_every']['interval']
140
+ time_unit = params['execute_every']['time_unit'].upper()
143
141
  if time_unit == 'MINUTES':
144
142
  pass
145
143
  elif time_unit == 'HOURS':
@@ -224,8 +222,6 @@ class RunJobHandler(SQLHandler):
224
222
 
225
223
  """
226
224
 
227
- _enabled = False
228
-
229
225
  def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
230
226
  res = FusionSQLResult()
231
227
  res.add_field('JobID', result.STRING)
@@ -288,8 +284,6 @@ class WaitOnJobsHandler(SQLHandler):
288
284
 
289
285
  """
290
286
 
291
- _enabled = False
292
-
293
287
  def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
294
288
  res = FusionSQLResult()
295
289
  res.add_field('Success', result.BOOL)
@@ -298,8 +292,8 @@ class WaitOnJobsHandler(SQLHandler):
298
292
 
299
293
  timeout_in_secs = None
300
294
  if params.get('with_timeout'):
301
- timeout_in_secs = params['with_timeout'][0]['time']
302
- time_unit = params['with_timeout'][-1]['time_unit'].upper()
295
+ timeout_in_secs = params['with_timeout']['time']
296
+ time_unit = params['with_timeout']['time_unit'].upper()
303
297
  if time_unit == 'SECONDS':
304
298
  pass
305
299
  elif time_unit == 'MINUTES':
@@ -359,8 +353,6 @@ class ShowJobsHandler(SQLHandler):
359
353
 
360
354
  """
361
355
 
362
- _enabled = False
363
-
364
356
  def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
365
357
  res = FusionSQLResult()
366
358
  res.add_field('JobID', result.STRING)
@@ -492,8 +484,6 @@ class ShowJobExecutionsHandler(SQLHandler):
492
484
  EXTENDED;
493
485
  """
494
486
 
495
- _enabled = False
496
-
497
487
  def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
498
488
  res = FusionSQLResult()
499
489
  res.add_field('ExecutionID', result.STRING)
@@ -564,8 +554,6 @@ class ShowJobParametersHandler(SQLHandler):
564
554
  SHOW JOB PARAMETERS FOR 'job1';
565
555
  """
566
556
 
567
- _enabled = False
568
-
569
557
  def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
570
558
  res = FusionSQLResult()
571
559
  res.add_field('Name', result.STRING)
@@ -606,8 +594,6 @@ class ShowJobRuntimesHandler(SQLHandler):
606
594
  SHOW JOB RUNTIMES;
607
595
  """
608
596
 
609
- _enabled = False
610
-
611
597
  def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
612
598
  res = FusionSQLResult()
613
599
  res.add_field('Name', result.STRING)
@@ -653,8 +639,6 @@ class DropJobHandler(SQLHandler):
653
639
  DROP JOBS 'job1', 'job2';
654
640
  """
655
641
 
656
- _enabled = False
657
-
658
642
  def run(self, params: Dict[str, Any]) -> Optional[FusionSQLResult]:
659
643
  res = FusionSQLResult()
660
644
  res.add_field('JobID', result.STRING)