singlestoredb 1.4.0__tar.gz → 1.4.2__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 (128) hide show
  1. {singlestoredb-1.4.0/singlestoredb.egg-info → singlestoredb-1.4.2}/PKG-INFO +1 -1
  2. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/accel.c +19 -9
  3. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/setup.cfg +1 -1
  4. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/__init__.py +1 -1
  5. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/connection.py +4 -0
  6. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/functions/signature.py +4 -2
  7. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/fusion/handler.py +107 -2
  8. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/management/__init__.py +1 -1
  9. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/management/cluster.py +4 -3
  10. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/management/manager.py +1 -2
  11. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/management/utils.py +68 -21
  12. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/management/workspace.py +5 -4
  13. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/connection.py +33 -4
  14. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/notebook/__init__.py +1 -0
  15. singlestoredb-1.4.2/singlestoredb/notebook/_portal.py +281 -0
  16. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/test_connection.py +24 -20
  17. singlestoredb-1.4.2/singlestoredb/utils/events.py +49 -0
  18. {singlestoredb-1.4.0 → singlestoredb-1.4.2/singlestoredb.egg-info}/PKG-INFO +1 -1
  19. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb.egg-info/SOURCES.txt +2 -0
  20. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/LICENSE +0 -0
  21. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/README.md +0 -0
  22. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/setup.py +0 -0
  23. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/ai/__init__.py +0 -0
  24. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/ai/embeddings.py +0 -0
  25. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/alchemy/__init__.py +0 -0
  26. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/auth.py +0 -0
  27. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/config.py +0 -0
  28. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/converters.py +0 -0
  29. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/exceptions.py +0 -0
  30. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/functions/__init__.py +0 -0
  31. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/functions/decorator.py +0 -0
  32. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/functions/dtypes.py +0 -0
  33. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/functions/ext/__init__.py +0 -0
  34. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/functions/ext/arrow.py +0 -0
  35. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/functions/ext/asgi.py +0 -0
  36. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/functions/ext/json.py +0 -0
  37. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/functions/ext/mmap.py +0 -0
  38. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/functions/ext/rowdat_1.py +0 -0
  39. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/functions/ext/utils.py +0 -0
  40. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/fusion/__init__.py +0 -0
  41. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/fusion/graphql.py +0 -0
  42. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/fusion/handlers/__init__.py +0 -0
  43. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/fusion/handlers/stage.py +0 -0
  44. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/fusion/handlers/utils.py +0 -0
  45. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/fusion/handlers/workspace.py +0 -0
  46. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/fusion/registry.py +0 -0
  47. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/fusion/result.py +0 -0
  48. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/http/__init__.py +0 -0
  49. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/http/connection.py +0 -0
  50. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/management/billing_usage.py +0 -0
  51. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/management/organization.py +0 -0
  52. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/management/region.py +0 -0
  53. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/__init__.py +0 -0
  54. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/_auth.py +0 -0
  55. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/charset.py +0 -0
  56. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/constants/CLIENT.py +0 -0
  57. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/constants/COMMAND.py +0 -0
  58. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/constants/CR.py +0 -0
  59. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/constants/ER.py +0 -0
  60. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/constants/EXTENDED_TYPE.py +0 -0
  61. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/constants/FIELD_TYPE.py +0 -0
  62. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/constants/FLAG.py +0 -0
  63. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/constants/SERVER_STATUS.py +0 -0
  64. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/constants/VECTOR_TYPE.py +0 -0
  65. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/constants/__init__.py +0 -0
  66. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/converters.py +0 -0
  67. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/cursors.py +0 -0
  68. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/err.py +0 -0
  69. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/optionfile.py +0 -0
  70. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/protocol.py +0 -0
  71. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/__init__.py +0 -0
  72. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/base.py +0 -0
  73. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/conftest.py +0 -0
  74. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/test_DictCursor.py +0 -0
  75. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/test_SSCursor.py +0 -0
  76. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/test_basic.py +0 -0
  77. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/test_connection.py +0 -0
  78. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/test_converters.py +0 -0
  79. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/test_cursor.py +0 -0
  80. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/test_err.py +0 -0
  81. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/test_issues.py +0 -0
  82. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/test_load_local.py +0 -0
  83. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/test_nextset.py +0 -0
  84. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/test_optionfile.py +0 -0
  85. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/thirdparty/__init__.py +0 -0
  86. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/thirdparty/test_MySQLdb/__init__.py +0 -0
  87. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/thirdparty/test_MySQLdb/capabilities.py +0 -0
  88. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/thirdparty/test_MySQLdb/dbapi20.py +0 -0
  89. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_capabilities.py +0 -0
  90. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_dbapi20.py +0 -0
  91. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/tests/thirdparty/test_MySQLdb/test_MySQLdb_nonstandard.py +0 -0
  92. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/mysql/times.py +0 -0
  93. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/notebook/_objects.py +0 -0
  94. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/pytest.py +0 -0
  95. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/__init__.py +0 -0
  96. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/empty.sql +0 -0
  97. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/ext_funcs/__init__.py +0 -0
  98. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/local_infile.csv +0 -0
  99. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/test.sql +0 -0
  100. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/test2.sql +0 -0
  101. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/test_basics.py +0 -0
  102. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/test_config.py +0 -0
  103. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/test_dbapi.py +0 -0
  104. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/test_exceptions.py +0 -0
  105. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/test_ext_func.py +0 -0
  106. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/test_ext_func_data.py +0 -0
  107. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/test_fusion.py +0 -0
  108. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/test_http.py +0 -0
  109. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/test_management.py +0 -0
  110. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/test_plugin.py +0 -0
  111. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/test_results.py +0 -0
  112. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/test_types.py +0 -0
  113. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/test_udf.py +0 -0
  114. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/test_xdict.py +0 -0
  115. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/tests/utils.py +0 -0
  116. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/types.py +0 -0
  117. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/utils/__init__.py +0 -0
  118. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/utils/config.py +0 -0
  119. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/utils/convert_rows.py +0 -0
  120. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/utils/debug.py +0 -0
  121. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/utils/dtypes.py +0 -0
  122. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/utils/mogrify.py +0 -0
  123. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/utils/results.py +0 -0
  124. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb/utils/xdict.py +0 -0
  125. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb.egg-info/dependency_links.txt +0 -0
  126. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb.egg-info/entry_points.txt +0 -0
  127. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/singlestoredb.egg-info/requires.txt +0 -0
  128. {singlestoredb-1.4.0 → singlestoredb-1.4.2}/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.4.0
3
+ Version: 1.4.2
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
@@ -421,6 +421,7 @@ typedef struct {
421
421
  PyObject *float64;
422
422
  PyObject *unpack;
423
423
  PyObject *decode;
424
+ PyObject *frombuffer;
424
425
  } PyStrings;
425
426
 
426
427
  static PyStrings PyStr = {0};
@@ -437,6 +438,7 @@ typedef struct {
437
438
  PyObject *datetime_datetime;
438
439
  PyObject *collections_namedtuple;
439
440
  PyObject *numpy_array;
441
+ PyObject *numpy_frombuffer;
440
442
  PyObject *numpy_vectorize;
441
443
  PyObject *pandas_DataFrame;
442
444
  PyObject *polars_DataFrame;
@@ -521,6 +523,9 @@ int ensure_numpy() {
521
523
  PyFunc.numpy_array = PyObject_GetAttr(numpy_mod, PyStr.array);
522
524
  if (!PyFunc.numpy_array) goto error;
523
525
 
526
+ PyFunc.numpy_frombuffer = PyObject_GetAttr(numpy_mod, PyStr.frombuffer);
527
+ if (!PyFunc.numpy_frombuffer) goto error;
528
+
524
529
  PyFunc.numpy_vectorize = PyObject_GetAttr(numpy_mod, PyStr.vectorize);
525
530
  if (!PyFunc.numpy_vectorize) goto error;
526
531
 
@@ -1476,7 +1481,7 @@ static PyObject *read_row_from_packet(
1476
1481
  PyObject *py_str = NULL;
1477
1482
  PyObject *py_memview = NULL;
1478
1483
  char end = '\0';
1479
- char *cast_type_codes[] = {"", "f", "d", "b", "h", "l", "q"};
1484
+ char *cast_type_codes[] = {"", "f", "d", "b", "h", "i", "q"};
1480
1485
  int item_type_lengths[] = {0, 4, 8, 1, 2, 4, 8};
1481
1486
 
1482
1487
  int sign = 1;
@@ -1773,17 +1778,20 @@ static PyObject *read_row_from_packet(
1773
1778
  case MYSQL_TYPE_INT16_VECTOR:
1774
1779
  case MYSQL_TYPE_INT32_VECTOR:
1775
1780
  case MYSQL_TYPE_INT64_VECTOR:
1781
+ {
1782
+ int type_idx = py_state->type_codes[i] % 1000;
1783
+
1776
1784
  if (ensure_numpy() == 0) {
1777
- py_memview = PyMemoryView_FromMemory(out, out_l, PyBUF_WRITE);
1785
+ py_memview = PyBytes_FromStringAndSize(out, out_l);
1778
1786
  if (!py_memview) goto error;
1779
1787
 
1780
- py_item = create_numpy_array(
1781
- py_memview,
1782
- cast_type_codes[py_state->type_codes[i] % 1000],
1783
- py_state->type_codes[i],
1784
- NULL
1788
+ CHECKRC(PyTuple_SetItem(PyObj.create_numpy_array_args, 0, py_memview));
1789
+
1790
+ py_item = PyObject_Call(
1791
+ PyFunc.numpy_frombuffer,
1792
+ PyObj.create_numpy_array_args,
1793
+ PyObj.create_numpy_array_kwargs_vector[type_idx]
1785
1794
  );
1786
- Py_CLEAR(py_memview);
1787
1795
  if (!py_item) goto error;
1788
1796
 
1789
1797
  } else {
@@ -1791,7 +1799,7 @@ static PyObject *read_row_from_packet(
1791
1799
  if (!py_memview) goto error;
1792
1800
 
1793
1801
  CHECKRC(PyTuple_SetItem(PyObj.struct_unpack_args, 0,
1794
- PyUnicode_FromFormat("<%l%s", out_l / item_type_lengths[i], cast_type_codes[i])));
1802
+ PyUnicode_FromFormat("<%ld%s", out_l / item_type_lengths[type_idx], cast_type_codes[type_idx])));
1795
1803
  CHECKRC(PyTuple_SetItem(PyObj.struct_unpack_args, 1, py_memview));
1796
1804
 
1797
1805
  py_item = PyObject_Call(
@@ -1803,6 +1811,7 @@ static PyObject *read_row_from_packet(
1803
1811
  }
1804
1812
 
1805
1813
  break;
1814
+ }
1806
1815
 
1807
1816
  case MYSQL_TYPE_BSON:
1808
1817
  py_item = PyBytes_FromStringAndSize(out, out_l);
@@ -4614,6 +4623,7 @@ PyMODINIT_FUNC PyInit__singlestoredb_accel(void) {
4614
4623
  PyStr.float64 = PyUnicode_FromString("float64");
4615
4624
  PyStr.unpack = PyUnicode_FromString("unpack");
4616
4625
  PyStr.decode = PyUnicode_FromString("decode");
4626
+ PyStr.frombuffer = PyUnicode_FromString("frombuffer");
4617
4627
 
4618
4628
  PyObject *decimal_mod = PyImport_ImportModule("decimal");
4619
4629
  if (!decimal_mod) goto error;
@@ -1,6 +1,6 @@
1
1
  [metadata]
2
2
  name = singlestoredb
3
- version = 1.4.0
3
+ version = 1.4.2
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
@@ -13,7 +13,7 @@ Examples
13
13
 
14
14
  """
15
15
 
16
- __version__ = '1.4.0'
16
+ __version__ = '1.4.2'
17
17
 
18
18
  from typing import Any
19
19
 
@@ -1359,6 +1359,10 @@ def connect(
1359
1359
  results_type : str, optional
1360
1360
  The form of the query results: tuples, namedtuples, dicts,
1361
1361
  numpy, polars, pandas, arrow
1362
+ buffered : bool, optional
1363
+ Should the entire query result be buffered in memory? This is the default
1364
+ behavior which allows full cursor control of the result, but does consume
1365
+ more memory.
1362
1366
  results_format : str, optional
1363
1367
  Deprecated. This option has been renamed to results_type.
1364
1368
  program_name : str, optional
@@ -45,15 +45,17 @@ if has_numpy:
45
45
  np.uint8: 'uint8',
46
46
  np.longlong: 'uint64',
47
47
  np.ulonglong: 'uint64',
48
- np.unicode_: 'str',
49
48
  np.str_: 'str',
50
49
  np.bytes_: 'bytes',
51
- np.float_: 'float64',
52
50
  np.float64: 'float64',
53
51
  np.float32: 'float32',
54
52
  np.float16: 'float16',
55
53
  np.double: 'float64',
56
54
  }
55
+ if hasattr(np, 'unicode_'):
56
+ numpy_type_map[np.unicode_] = 'str'
57
+ if hasattr(np, 'float_'):
58
+ numpy_type_map[np.float_] = 'float64'
57
59
  else:
58
60
  array_types = (Sequence,)
59
61
  numpy_type_map = {}
@@ -28,8 +28,23 @@ CORE_GRAMMAR = r'''
28
28
  eq = ws* "=" ws*
29
29
  open_paren = ws* "(" ws*
30
30
  close_paren = ws* ")" ws*
31
+ open_repeats = ws* ~r"[\(\[\{]" ws*
32
+ close_repeats = ws* ~r"[\)\]\}]" ws*
31
33
  select = ~r"SELECT"i ws+ ~r".+" ws*
32
- '''
34
+
35
+ json = ws* json_object ws*
36
+ json_object = ~r"{\s*" json_members? ~r"\s*}"
37
+ json_members = json_mapping (~r"\s*,\s*" json_mapping)*
38
+ json_mapping = json_string ~r"\s*:\s*" json_value
39
+ json_array = ~r"\[\s*" json_items? ~r"\s*\]"
40
+ json_items = json_value (~r"\s*,\s*" json_value)*
41
+ json_value = json_object / json_array / json_string / json_true_val / json_false_val / json_null_val / json_number
42
+ json_true_val = "true"
43
+ json_false_val = "false"
44
+ json_null_val = "null"
45
+ json_string = ~r"\"[ !#-\[\]-\U0010ffff]*(?:\\(?:[\"\\/bfnrt]|u[0-9A-Fa-f]{4})[ !#-\[\]-\U0010ffff]*)*\""
46
+ json_number = ~r"-?(0|[1-9][0-9]*)(\.\d*)?([eE][-+]?\d+)?"
47
+ ''' # noqa: E501
33
48
 
34
49
  BUILTINS = {
35
50
  '<order-by>': r'''
@@ -47,6 +62,7 @@ BUILTINS = {
47
62
  ''',
48
63
  '<integer>': '',
49
64
  '<number>': '',
65
+ '<json>': '',
50
66
  }
51
67
 
52
68
  BUILTIN_DEFAULTS = { # type: ignore
@@ -54,9 +70,36 @@ BUILTIN_DEFAULTS = { # type: ignore
54
70
  'like': None,
55
71
  'extended': False,
56
72
  'limit': None,
73
+ 'json': {},
74
+ }
75
+
76
+ _json_unesc_re = re.compile(r'\\(["/\\bfnrt]|u[0-9A-Fa-f])')
77
+ _json_unesc_map = {
78
+ '"': '"',
79
+ '/': '/',
80
+ '\\': '\\',
81
+ 'b': '\b',
82
+ 'f': '\f',
83
+ 'n': '\n',
84
+ 'r': '\r',
85
+ 't': '\t',
57
86
  }
58
87
 
59
88
 
89
+ def _json_unescape(m: Any) -> str:
90
+ c = m.group(1)
91
+ if c[0] == 'u':
92
+ return chr(int(c[1:], 16))
93
+ c2 = _json_unesc_map.get(c)
94
+ if not c2:
95
+ raise ValueError(f'invalid escape sequence: {m.group(0)}')
96
+ return c2
97
+
98
+
99
+ def json_unescape(s: str) -> str:
100
+ return _json_unesc_re.sub(_json_unescape, s[1:-1])
101
+
102
+
60
103
  def get_keywords(grammar: str) -> Tuple[str, ...]:
61
104
  """Return all all-caps words from the beginning of the line."""
62
105
  m = re.match(r'^\s*((?:[A-Z0-9_]+)(\s+|$|;))+', grammar)
@@ -89,7 +132,7 @@ def process_alternates(m: Any) -> str:
89
132
  def process_repeats(m: Any) -> str:
90
133
  """Add repeated patterns."""
91
134
  sql = m.group(1).strip()
92
- return f'open_paren? {sql} ws* ( comma {sql} ws* )* close_paren?'
135
+ return f'open_repeats? {sql} ws* ( comma {sql} ws* )* close_repeats?'
93
136
 
94
137
 
95
138
  def lower_and_regex(m: Any) -> str:
@@ -639,6 +682,14 @@ class SQLHandler(NodeVisitor):
639
682
  """Close parenthesis."""
640
683
  return
641
684
 
685
+ def visit_open_repeats(self, node: Node, visited_children: Iterable[Any]) -> Any:
686
+ """Open repeat grouping."""
687
+ return
688
+
689
+ def visit_close_repeats(self, node: Node, visited_children: Iterable[Any]) -> Any:
690
+ """Close repeat grouping."""
691
+ return
692
+
642
693
  def visit_init(self, node: Node, visited_children: Iterable[Any]) -> Any:
643
694
  """Entry point of the grammar."""
644
695
  _, out, *_ = visited_children
@@ -662,6 +713,57 @@ class SQLHandler(NodeVisitor):
662
713
  ascending.append(value[1].upper().startswith('A'))
663
714
  return {'order_by': {'by': by, 'ascending': ascending}}
664
715
 
716
+ def _delimited(self, node: Node, children: Iterable[Any]) -> Any:
717
+ children = list(children)
718
+ items = [children[0]]
719
+ items.extend(item for _, item in children[1])
720
+ return items
721
+
722
+ def _atomic(self, node: Node, children: Iterable[Any]) -> Any:
723
+ return list(children)[0]
724
+
725
+ # visitors
726
+ visit_json_value = _atomic
727
+ visit_json_members = visit_json_items = _delimited
728
+
729
+ def visit_json_object(self, node: Node, children: Iterable[Any]) -> Any:
730
+ _, members, _ = children
731
+ if isinstance(members, list):
732
+ members = members[0]
733
+ else:
734
+ members = []
735
+ members = [x for x in members if x != '']
736
+ return dict(members)
737
+
738
+ def visit_json_array(self, node: Node, children: Iterable[Any]) -> Any:
739
+ _, values, _ = children
740
+ if isinstance(values, list):
741
+ values = values[0]
742
+ else:
743
+ values = []
744
+ return values
745
+
746
+ def visit_json_mapping(self, node: Node, children: Iterable[Any]) -> Any:
747
+ key, _, value = children
748
+ return key, value
749
+
750
+ def visit_json_string(self, node: Node, children: Iterable[Any]) -> Any:
751
+ return json_unescape(node.text)
752
+
753
+ def visit_json_number(self, node: Node, children: Iterable[Any]) -> Any:
754
+ if '.' in node.text:
755
+ return float(node.text)
756
+ return int(node.text)
757
+
758
+ def visit_json_true_val(self, node: Node, children: Iterable[Any]) -> Any:
759
+ return True
760
+
761
+ def visit_json_false_val(self, node: Node, children: Iterable[Any]) -> Any:
762
+ return False
763
+
764
+ def visit_json_null_val(self, node: Node, children: Iterable[Any]) -> Any:
765
+ return None
766
+
665
767
  def generic_visit(self, node: Node, visited_children: Iterable[Any]) -> Any:
666
768
  """
667
769
  Handle all undefined rules.
@@ -675,6 +777,9 @@ class SQLHandler(NodeVisitor):
675
777
  rule can have repeated values, a list of values is returned.
676
778
 
677
779
  """
780
+ if node.expr_name.startswith('json'):
781
+ return visited_children or node.text
782
+
678
783
  # Call a grammar rule
679
784
  if node.expr_name in type(self).rule_info:
680
785
  n_keywords = type(self).rule_info[node.expr_name]['n_keywords']
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python
2
2
  from .cluster import manage_cluster
3
- from .manager import get_organization
4
3
  from .manager import get_token
4
+ from .workspace import get_organization
5
5
  from .workspace import get_secret
6
6
  from .workspace import get_stage
7
7
  from .workspace import manage_workspaces
@@ -8,6 +8,7 @@ from typing import List
8
8
  from typing import Optional
9
9
  from typing import Union
10
10
 
11
+ from .. import config
11
12
  from .. import connection
12
13
  from ..exceptions import ManagementError
13
14
  from .manager import Manager
@@ -332,7 +333,7 @@ class ClusterManager(Manager):
332
333
  default_version = 'v0beta'
333
334
 
334
335
  #: Base URL if none is specified.
335
- default_base_url = 'https://api.singlestore.com'
336
+ default_base_url = config.get_option('management.base_url')
336
337
 
337
338
  #: Object type
338
339
  obj_type = 'cluster'
@@ -425,8 +426,8 @@ class ClusterManager(Manager):
425
426
 
426
427
  def manage_cluster(
427
428
  access_token: Optional[str] = None,
428
- version: str = ClusterManager.default_version,
429
- base_url: str = ClusterManager.default_base_url,
429
+ version: Optional[str] = None,
430
+ base_url: Optional[str] = None,
430
431
  *,
431
432
  organization_id: Optional[str] = None,
432
433
  ) -> ClusterManager:
@@ -15,7 +15,6 @@ import requests
15
15
 
16
16
  from .. import config
17
17
  from ..exceptions import ManagementError
18
- from .utils import get_organization
19
18
  from .utils import get_token
20
19
 
21
20
 
@@ -24,7 +23,7 @@ def set_organization(kwargs: Dict[str, Any]) -> None:
24
23
  if kwargs.get('params', {}).get('organizationID', None):
25
24
  return
26
25
 
27
- org = get_organization()
26
+ org = os.environ.get('SINGLESTOREDB_ORGANIZATION')
28
27
  if org:
29
28
  if 'params' not in kwargs:
30
29
  kwargs['params'] = {}
@@ -2,6 +2,7 @@
2
2
  """SingleStoreDB Cluster Management."""
3
3
  import datetime
4
4
  import functools
5
+ import itertools
5
6
  import os
6
7
  import re
7
8
  import sys
@@ -12,6 +13,7 @@ from typing import List
12
13
  from typing import Mapping
13
14
  from typing import Optional
14
15
  from typing import SupportsIndex
16
+ from typing import Tuple
15
17
  from typing import TypeVar
16
18
  from typing import Union
17
19
  from urllib.parse import urlparse
@@ -20,6 +22,7 @@ import jwt
20
22
 
21
23
  from .. import converters
22
24
  from ..config import get_option
25
+ from ..utils import events
23
26
 
24
27
  JSON = Union[str, List[str], Dict[str, 'JSON']]
25
28
  JSONObj = Dict[str, JSON]
@@ -117,6 +120,66 @@ class NamedList(List[T]):
117
120
  raise
118
121
 
119
122
 
123
+ def _setup_authentication_info_handler() -> Callable[..., Dict[str, Any]]:
124
+ """Setup authentication info event handler."""
125
+
126
+ authentication_info: List[Tuple[str, Any]] = []
127
+
128
+ def handle_authentication_info(msg: Dict[str, Any]) -> None:
129
+ """Handle authentication info events."""
130
+ nonlocal authentication_info
131
+ if msg.get('name', '') != 'singlestore.portal.authentication_updated':
132
+ return
133
+ authentication_info = list(msg.get('data', {}).items())
134
+
135
+ events.subscribe(handle_authentication_info)
136
+
137
+ def handle_connection_info(msg: Dict[str, Any]) -> None:
138
+ """Handle connection info events."""
139
+ nonlocal authentication_info
140
+ if msg.get('name', '') != 'singlestore.portal.connection_updated':
141
+ return
142
+ data = msg.get('data', {})
143
+ out = {}
144
+ if 'user' in data:
145
+ out['user'] = data['user']
146
+ if 'password' in data:
147
+ out['password'] = data['password']
148
+ authentication_info = list(out.items())
149
+
150
+ events.subscribe(handle_authentication_info)
151
+
152
+ def get_env() -> List[Tuple[str, Any]]:
153
+ conn = {}
154
+ url = os.environ.get('SINGLESTOREDB_URL') or get_option('host')
155
+ if url:
156
+ urlp = urlparse(url, scheme='singlestoredb', allow_fragments=True)
157
+ conn = dict(
158
+ user=urlp.username or None,
159
+ password=urlp.password or None,
160
+ )
161
+
162
+ return [
163
+ x for x in dict(
164
+ **conn,
165
+ ).items() if x[1] is not None
166
+ ]
167
+
168
+ def get_authentication_info(include_env: bool = True) -> Dict[str, Any]:
169
+ """Return authentication info from event."""
170
+ return dict(
171
+ itertools.chain(
172
+ (get_env() if include_env else []),
173
+ authentication_info,
174
+ ),
175
+ )
176
+
177
+ return get_authentication_info
178
+
179
+
180
+ get_authentication_info = _setup_authentication_info_handler()
181
+
182
+
120
183
  def get_token() -> Optional[str]:
121
184
  """Return the token for the Management API."""
122
185
  # See if an API key is configured
@@ -124,18 +187,11 @@ def get_token() -> Optional[str]:
124
187
  if tok:
125
188
  return tok
126
189
 
127
- url = os.environ.get('SINGLESTOREDB_URL')
128
- if not url:
129
- # See if the connection URL contains a JWT
130
- url = get_option('host')
131
- if not url:
132
- return None
133
-
134
- urlp = urlparse(url, scheme='singlestoredb', allow_fragments=True)
135
- if urlp.password:
190
+ tok = get_authentication_info(include_env=True).get('password')
191
+ if tok:
136
192
  try:
137
- jwt.decode(urlp.password, options={'verify_signature': False})
138
- return urlp.password
193
+ jwt.decode(tok, options={'verify_signature': False})
194
+ return tok
139
195
  except jwt.DecodeError:
140
196
  pass
141
197
 
@@ -143,15 +199,6 @@ def get_token() -> Optional[str]:
143
199
  return None
144
200
 
145
201
 
146
- def get_organization() -> Optional[str]:
147
- """Return the organization for the current token or environment."""
148
- org = os.environ.get('SINGLESTOREDB_ORGANIZATION')
149
- if org:
150
- return org
151
-
152
- return None
153
-
154
-
155
202
  def enable_http_tracing() -> None:
156
203
  """Enable tracing of HTTP requests."""
157
204
  import logging
@@ -269,7 +316,7 @@ def snake_to_camel(s: Optional[str], cap_first: bool = False) -> Optional[str]:
269
316
  """Convert snake-case to camel-case."""
270
317
  if s is None:
271
318
  return None
272
- out = re.sub(r'_[A-Za-z]', _upper_match, s.lower())
319
+ out = re.sub(r'_([A-Za-z])', _upper_match, s.lower())
273
320
  if cap_first and out:
274
321
  return out[0].upper() + out[1:]
275
322
  return out
@@ -17,6 +17,7 @@ from typing import Optional
17
17
  from typing import TextIO
18
18
  from typing import Union
19
19
 
20
+ from .. import config
20
21
  from .. import connection
21
22
  from ..exceptions import ManagementError
22
23
  from .billing_usage import BillingUsageItem
@@ -1690,10 +1691,10 @@ class WorkspaceManager(Manager):
1690
1691
  """
1691
1692
 
1692
1693
  #: Workspace management API version if none is specified.
1693
- default_version = 'v1'
1694
+ default_version = config.get_option('management.version')
1694
1695
 
1695
1696
  #: Base URL if none is specified.
1696
- default_base_url = 'https://api.singlestore.com'
1697
+ default_base_url = config.get_option('management.base_url')
1697
1698
 
1698
1699
  #: Object type
1699
1700
  obj_type = 'workspace'
@@ -1906,8 +1907,8 @@ class WorkspaceManager(Manager):
1906
1907
 
1907
1908
  def manage_workspaces(
1908
1909
  access_token: Optional[str] = None,
1909
- version: str = WorkspaceManager.default_version,
1910
- base_url: str = WorkspaceManager.default_base_url,
1910
+ version: Optional[str] = None,
1911
+ base_url: Optional[str] = None,
1911
1912
  *,
1912
1913
  organization_id: Optional[str] = None,
1913
1914
  ) -> WorkspaceManager:
@@ -13,6 +13,8 @@ import struct
13
13
  import sys
14
14
  import traceback
15
15
  import warnings
16
+ from typing import Any
17
+ from typing import Dict
16
18
  from typing import Iterable
17
19
 
18
20
  try:
@@ -21,6 +23,7 @@ except (ImportError, ModuleNotFoundError):
21
23
  _singlestoredb_accel = None
22
24
 
23
25
  from . import _auth
26
+ from ..utils import events
24
27
 
25
28
  from .charset import charset_by_name, charset_by_id
26
29
  from .constants import CLIENT, COMMAND, CR, ER, FIELD_TYPE, SERVER_STATUS
@@ -100,6 +103,19 @@ TEXT_TYPES = {
100
103
  FIELD_TYPE.VAR_STRING,
101
104
  FIELD_TYPE.VARCHAR,
102
105
  FIELD_TYPE.GEOMETRY,
106
+ FIELD_TYPE.BSON,
107
+ FIELD_TYPE.FLOAT32_VECTOR_JSON,
108
+ FIELD_TYPE.FLOAT64_VECTOR_JSON,
109
+ FIELD_TYPE.INT8_VECTOR_JSON,
110
+ FIELD_TYPE.INT16_VECTOR_JSON,
111
+ FIELD_TYPE.INT32_VECTOR_JSON,
112
+ FIELD_TYPE.INT64_VECTOR_JSON,
113
+ FIELD_TYPE.FLOAT32_VECTOR,
114
+ FIELD_TYPE.FLOAT64_VECTOR,
115
+ FIELD_TYPE.INT8_VECTOR,
116
+ FIELD_TYPE.INT16_VECTOR,
117
+ FIELD_TYPE.INT32_VECTOR,
118
+ FIELD_TYPE.INT64_VECTOR,
103
119
  }
104
120
 
105
121
  UNSET = 'unset'
@@ -614,15 +630,22 @@ class Connection(BaseConnection):
614
630
  if k not in self._connect_attrs:
615
631
  self._connect_attrs[k] = v
616
632
 
633
+ self._is_committable = True
617
634
  self._in_sync = False
618
635
  self._track_env = bool(track_env) or self.host == 'singlestore.com'
619
636
  self._enable_extended_data_types = enable_extended_data_types
637
+ self._connection_info = {}
638
+ events.subscribe(self._handle_event)
620
639
 
621
640
  if defer_connect or self._track_env:
622
641
  self._sock = None
623
642
  else:
624
643
  self.connect()
625
644
 
645
+ def _handle_event(self, data: Dict[str, Any]) -> None:
646
+ if data.get('name', '') == 'singlestore.portal.connection_updated':
647
+ self._connection_info = dict(data)
648
+
626
649
  @property
627
650
  def messages(self):
628
651
  # TODO
@@ -766,7 +789,8 @@ class Connection(BaseConnection):
766
789
 
767
790
  """
768
791
  log_query('COMMIT')
769
- if self.host == 'singlestore.com':
792
+ if not self._is_committable or self.host == 'singlestore.com':
793
+ self._is_committable = True
770
794
  return
771
795
  self._execute_command(COMMAND.COM_QUERY, 'COMMIT')
772
796
  self._read_ok_packet()
@@ -780,7 +804,8 @@ class Connection(BaseConnection):
780
804
 
781
805
  """
782
806
  log_query('ROLLBACK')
783
- if self.host == 'singlestore.com':
807
+ if not self._is_committable or self.host == 'singlestore.com':
808
+ self._is_committable = True
784
809
  return
785
810
  self._execute_command(COMMAND.COM_QUERY, 'ROLLBACK')
786
811
  self._read_ok_packet()
@@ -858,9 +883,11 @@ class Connection(BaseConnection):
858
883
  # print("DEBUG: sending query:", sql)
859
884
  handler = fusion.get_handler(sql)
860
885
  if handler is not None:
886
+ self._is_committable = False
861
887
  self._result = fusion.execute(self, sql, handler=handler)
862
888
  self._affected_rows = self._result.affected_rows
863
889
  else:
890
+ self._is_committable = True
864
891
  if isinstance(sql, str):
865
892
  sql = sql.encode(self.encoding, 'surrogateescape')
866
893
  self._local_infile_stream = infile_stream
@@ -973,9 +1000,11 @@ class Connection(BaseConnection):
973
1000
  if not self._track_env:
974
1001
  return
975
1002
 
976
- url = os.environ.get('SINGLESTOREDB_URL')
1003
+ url = self._connection_info.get('connection_url')
977
1004
  if not url:
978
- return
1005
+ url = os.environ.get('SINGLESTOREDB_URL')
1006
+ if not url:
1007
+ return
979
1008
 
980
1009
  out = {}
981
1010
  urlp = connection._parse_url(url)
@@ -7,6 +7,7 @@ from ._objects import secrets # noqa: F401
7
7
  from ._objects import stage # noqa: F401
8
8
  from ._objects import workspace # noqa: F401
9
9
  from ._objects import workspace_group # noqa: F401
10
+ from ._portal import portal # noqa: F401
10
11
 
11
12
  if 'SINGLESTOREDB_ORGANIZATION' not in _os.environ:
12
13
  _warnings.warn(