vastdb 1.3.1__py3-none-any.whl → 1.3.3__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.
@@ -0,0 +1,104 @@
1
+ # automatically generated by the FlatBuffers compiler, do not modify
2
+
3
+ # namespace: tabular
4
+
5
+ import flatbuffers
6
+ from flatbuffers.compat import import_numpy
7
+ np = import_numpy()
8
+
9
+ class CreateViewRequest(object):
10
+ __slots__ = ['_tab']
11
+
12
+ @classmethod
13
+ def GetRootAs(cls, buf, offset=0):
14
+ n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
15
+ x = CreateViewRequest()
16
+ x.Init(buf, n + offset)
17
+ return x
18
+
19
+ @classmethod
20
+ def GetRootAsCreateViewRequest(cls, buf, offset=0):
21
+ """This method is deprecated. Please switch to GetRootAs."""
22
+ return cls.GetRootAs(buf, offset)
23
+ # CreateViewRequest
24
+ def Init(self, buf, pos):
25
+ self._tab = flatbuffers.table.Table(buf, pos)
26
+
27
+ # CreateViewRequest
28
+ def ViewMetadataArrowBuffer(self, j):
29
+ o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
30
+ if o != 0:
31
+ a = self._tab.Vector(o)
32
+ return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
33
+ return 0
34
+
35
+ # CreateViewRequest
36
+ def ViewMetadataArrowBufferAsNumpy(self):
37
+ o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
38
+ if o != 0:
39
+ return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
40
+ return 0
41
+
42
+ # CreateViewRequest
43
+ def ViewMetadataArrowBufferLength(self):
44
+ o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
45
+ if o != 0:
46
+ return self._tab.VectorLen(o)
47
+ return 0
48
+
49
+ # CreateViewRequest
50
+ def ViewMetadataArrowBufferIsNone(self):
51
+ o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
52
+ return o == 0
53
+
54
+ # CreateViewRequest
55
+ def ViewDataArrowSchema(self, j):
56
+ o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
57
+ if o != 0:
58
+ a = self._tab.Vector(o)
59
+ return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
60
+ return 0
61
+
62
+ # CreateViewRequest
63
+ def ViewDataArrowSchemaAsNumpy(self):
64
+ o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
65
+ if o != 0:
66
+ return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
67
+ return 0
68
+
69
+ # CreateViewRequest
70
+ def ViewDataArrowSchemaLength(self):
71
+ o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
72
+ if o != 0:
73
+ return self._tab.VectorLen(o)
74
+ return 0
75
+
76
+ # CreateViewRequest
77
+ def ViewDataArrowSchemaIsNone(self):
78
+ o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
79
+ return o == 0
80
+
81
+ def Start(builder): builder.StartObject(2)
82
+ def CreateViewRequestStart(builder):
83
+ """This method is deprecated. Please switch to Start."""
84
+ return Start(builder)
85
+ def AddViewMetadataArrowBuffer(builder, viewMetadataArrowBuffer): builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(viewMetadataArrowBuffer), 0)
86
+ def CreateViewRequestAddViewMetadataArrowBuffer(builder, viewMetadataArrowBuffer):
87
+ """This method is deprecated. Please switch to AddViewMetadataArrowBuffer."""
88
+ return AddViewMetadataArrowBuffer(builder, viewMetadataArrowBuffer)
89
+ def StartViewMetadataArrowBufferVector(builder, numElems): return builder.StartVector(1, numElems, 1)
90
+ def CreateViewRequestStartViewMetadataArrowBufferVector(builder, numElems):
91
+ """This method is deprecated. Please switch to Start."""
92
+ return StartViewMetadataArrowBufferVector(builder, numElems)
93
+ def AddViewDataArrowSchema(builder, viewDataArrowSchema): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(viewDataArrowSchema), 0)
94
+ def CreateViewRequestAddViewDataArrowSchema(builder, viewDataArrowSchema):
95
+ """This method is deprecated. Please switch to AddViewDataArrowSchema."""
96
+ return AddViewDataArrowSchema(builder, viewDataArrowSchema)
97
+ def StartViewDataArrowSchemaVector(builder, numElems): return builder.StartVector(1, numElems, 1)
98
+ def CreateViewRequestStartViewDataArrowSchemaVector(builder, numElems):
99
+ """This method is deprecated. Please switch to Start."""
100
+ return StartViewDataArrowSchemaVector(builder, numElems)
101
+ def End(builder): return builder.EndObject()
102
+ def CreateViewRequestEnd(builder):
103
+ """This method is deprecated. Please switch to End."""
104
+ return End(builder)
@@ -0,0 +1,89 @@
1
+ # automatically generated by the FlatBuffers compiler, do not modify
2
+
3
+ # namespace: tabular
4
+
5
+ import flatbuffers
6
+ from flatbuffers.compat import import_numpy
7
+ np = import_numpy()
8
+
9
+ class ListViewsResponse(object):
10
+ __slots__ = ['_tab']
11
+
12
+ @classmethod
13
+ def GetRootAs(cls, buf, offset=0):
14
+ n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
15
+ x = ListViewsResponse()
16
+ x.Init(buf, n + offset)
17
+ return x
18
+
19
+ @classmethod
20
+ def GetRootAsListViewsResponse(cls, buf, offset=0):
21
+ """This method is deprecated. Please switch to GetRootAs."""
22
+ return cls.GetRootAs(buf, offset)
23
+ # ListViewsResponse
24
+ def Init(self, buf, pos):
25
+ self._tab = flatbuffers.table.Table(buf, pos)
26
+
27
+ # ListViewsResponse
28
+ def BucketName(self):
29
+ o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
30
+ if o != 0:
31
+ return self._tab.String(o + self._tab.Pos)
32
+ return None
33
+
34
+ # ListViewsResponse
35
+ def SchemaName(self):
36
+ o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
37
+ if o != 0:
38
+ return self._tab.String(o + self._tab.Pos)
39
+ return None
40
+
41
+ # ListViewsResponse
42
+ def Views(self, j):
43
+ o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
44
+ if o != 0:
45
+ x = self._tab.Vector(o)
46
+ x += flatbuffers.number_types.UOffsetTFlags.py_type(j) * 4
47
+ x = self._tab.Indirect(x)
48
+ from vast_flatbuf.tabular.ObjectDetails import ObjectDetails
49
+ obj = ObjectDetails()
50
+ obj.Init(self._tab.Bytes, x)
51
+ return obj
52
+ return None
53
+
54
+ # ListViewsResponse
55
+ def ViewsLength(self):
56
+ o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
57
+ if o != 0:
58
+ return self._tab.VectorLen(o)
59
+ return 0
60
+
61
+ # ListViewsResponse
62
+ def ViewsIsNone(self):
63
+ o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
64
+ return o == 0
65
+
66
+ def Start(builder): builder.StartObject(3)
67
+ def ListViewsResponseStart(builder):
68
+ """This method is deprecated. Please switch to Start."""
69
+ return Start(builder)
70
+ def AddBucketName(builder, bucketName): builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(bucketName), 0)
71
+ def ListViewsResponseAddBucketName(builder, bucketName):
72
+ """This method is deprecated. Please switch to AddBucketName."""
73
+ return AddBucketName(builder, bucketName)
74
+ def AddSchemaName(builder, schemaName): builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(schemaName), 0)
75
+ def ListViewsResponseAddSchemaName(builder, schemaName):
76
+ """This method is deprecated. Please switch to AddSchemaName."""
77
+ return AddSchemaName(builder, schemaName)
78
+ def AddViews(builder, views): builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(views), 0)
79
+ def ListViewsResponseAddViews(builder, views):
80
+ """This method is deprecated. Please switch to AddViews."""
81
+ return AddViews(builder, views)
82
+ def StartViewsVector(builder, numElems): return builder.StartVector(4, numElems, 4)
83
+ def ListViewsResponseStartViewsVector(builder, numElems):
84
+ """This method is deprecated. Please switch to Start."""
85
+ return StartViewsVector(builder, numElems)
86
+ def End(builder): return builder.EndObject()
87
+ def ListViewsResponseEnd(builder):
88
+ """This method is deprecated. Please switch to End."""
89
+ return End(builder)
@@ -60,7 +60,21 @@ class ObjectDetails(object):
60
60
  return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
61
61
  return 0
62
62
 
63
- def Start(builder): builder.StartObject(5)
63
+ # ObjectDetails
64
+ def NumPartitions(self):
65
+ o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(14))
66
+ if o != 0:
67
+ return self._tab.Get(flatbuffers.number_types.Uint64Flags, o + self._tab.Pos)
68
+ return 0
69
+
70
+ # ObjectDetails
71
+ def SortingKeyEnabled(self):
72
+ o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(16))
73
+ if o != 0:
74
+ return bool(self._tab.Get(flatbuffers.number_types.BoolFlags, o + self._tab.Pos))
75
+ return False
76
+
77
+ def Start(builder): builder.StartObject(7)
64
78
  def ObjectDetailsStart(builder):
65
79
  """This method is deprecated. Please switch to Start."""
66
80
  return Start(builder)
@@ -84,6 +98,14 @@ def AddSizeInBytes(builder, sizeInBytes): builder.PrependUint64Slot(4, sizeInByt
84
98
  def ObjectDetailsAddSizeInBytes(builder, sizeInBytes):
85
99
  """This method is deprecated. Please switch to AddSizeInBytes."""
86
100
  return AddSizeInBytes(builder, sizeInBytes)
101
+ def AddNumPartitions(builder, numPartitions): builder.PrependUint64Slot(5, numPartitions, 0)
102
+ def ObjectDetailsAddNumPartitions(builder, numPartitions):
103
+ """This method is deprecated. Please switch to AddNumPartitions."""
104
+ return AddNumPartitions(builder, numPartitions)
105
+ def AddSortingKeyEnabled(builder, sortingKeyEnabled): builder.PrependBoolSlot(6, sortingKeyEnabled, 0)
106
+ def ObjectDetailsAddSortingKeyEnabled(builder, sortingKeyEnabled):
107
+ """This method is deprecated. Please switch to AddSortingKeyEnabled."""
108
+ return AddSortingKeyEnabled(builder, sortingKeyEnabled)
87
109
  def End(builder): return builder.EndObject()
88
110
  def ObjectDetailsEnd(builder):
89
111
  """This method is deprecated. Please switch to End."""
vastdb/_internal.py CHANGED
@@ -735,17 +735,57 @@ def _iter_nested_arrays(column: pa.Array) -> Iterator[pa.Array]:
735
735
  yield from _iter_nested_arrays(column.values) # Note: Map is serialized in VAST as a List<Struct<K, V>>
736
736
 
737
737
 
738
- TableInfo = namedtuple('TableInfo', 'name properties handle num_rows size_in_bytes')
738
+ class ValidateInList:
739
+ def __init__(self, *args):
740
+ self.candidates = [x.strip() for x in args]
739
741
 
742
+ def __call__(self, x):
743
+ x = x.strip()
744
+ if x not in self.candidates:
745
+ raise Exception(f'{x} is not in {self.candidates}')
746
+ return x
747
+
748
+ def __getitem__(self, x):
749
+ return self
750
+
751
+
752
+ _int_coding = (lambda x: str(int(x)), lambda x: int(x))
753
+ _prop_coding = {
754
+ "message.timestamp.type": ValidateInList('CreateTime', 'LogAppendTime'),
755
+ "retention.ms": _int_coding,
756
+ "message.timestamp.after.max.ms": _int_coding,
757
+ "message.timestamp.before.max.ms": _int_coding
758
+ }
759
+
760
+
761
+ def _encode_table_props(**kwargs):
762
+ if all([v is None for v in kwargs.values()]):
763
+ return None
764
+ else:
765
+ pairs = [(k.replace("_", ".").strip(), v) for k, v in kwargs.items() if v is not None]
766
+ return "$".join([f"{k}={_prop_coding[k][0](v)}" for k, v in pairs])
767
+
768
+
769
+ def _decode_table_props(s):
770
+ if s.strip() == '':
771
+ return {}
772
+ triplets = [(x.strip(), x.strip().replace(".", "_"), y.strip()) for x, y in [z.split('=') for z in s.strip().split("$")]]
773
+ return {y: _prop_coding[x][1](z) for x, y, z in triplets if z != ''}
740
774
 
741
- def _parse_table_info(obj):
742
775
 
776
+ TableInfo = namedtuple('TableInfo', 'name properties handle num_rows size_in_bytes num_partitions')
777
+
778
+
779
+ def _parse_table_info(obj):
743
780
  name = obj.Name().decode()
744
781
  properties = obj.Properties().decode()
745
782
  handle = obj.Handle().decode()
746
783
  num_rows = obj.NumRows()
747
784
  used_bytes = obj.SizeInBytes()
748
- return TableInfo(name, properties, handle, num_rows, used_bytes)
785
+ num_partitions = obj.NumPartitions()
786
+ if num_partitions != 0:
787
+ properties = _decode_table_props(properties)
788
+ return TableInfo(name, properties, handle, num_rows, used_bytes, num_partitions)
749
789
 
750
790
 
751
791
  # Results that returns from tablestats
@@ -774,7 +814,13 @@ def _backoff_giveup(exc: Exception) -> bool:
774
814
  return True # give up in case of other exceptions
775
815
 
776
816
 
817
+ class UnsupportedServer(NotImplementedError):
818
+ """Raised when the response comes back from non-VAST DB server."""
819
+ pass
820
+
821
+
777
822
  class VastdbApi:
823
+ VAST_SERVER_PREFIX = 'vast'
778
824
  # we expect the vast version to be <major>.<minor>.<patch>.<protocol>
779
825
  VAST_VERSION_REGEX = re.compile(r'^vast (\d+\.\d+\.\d+\.\d+)$')
780
826
 
@@ -829,6 +875,9 @@ class VastdbApi:
829
875
  if server_header is None:
830
876
  _logger.error("Response doesn't contain 'Server' header")
831
877
  else:
878
+ if not server_header.startswith(self.VAST_SERVER_PREFIX):
879
+ raise UnsupportedServer(f'{self.url} is not a VAST DB server endpoint ("{server_header}")')
880
+
832
881
  if m := self.VAST_VERSION_REGEX.match(server_header):
833
882
  self.vast_version: Tuple[int, ...] = tuple(int(v) for v in m.group(1).split("."))
834
883
  return
@@ -1049,8 +1098,9 @@ class VastdbApi:
1049
1098
 
1050
1099
  return snapshots, is_truncated, marker
1051
1100
 
1052
- def create_table(self, bucket, schema, name, arrow_schema, txid=0, client_tags=[], expected_retvals=[],
1053
- topic_partitions=0, create_imports_table=False, use_external_row_ids_allocation=False):
1101
+ def create_table(self, bucket, schema, name, arrow_schema=None, txid=0, client_tags=[], expected_retvals=[],
1102
+ topic_partitions=0, create_imports_table=False, use_external_row_ids_allocation=False,
1103
+ message_timestamp_type=None, retention_ms=None, message_timestamp_after_max_ms=None, message_timestamp_before_max_ms=None):
1054
1104
  """
1055
1105
  Create a table, use the following request
1056
1106
  POST /bucket/schema/table?table HTTP/1.1
@@ -1069,6 +1119,9 @@ class VastdbApi:
1069
1119
  """
1070
1120
  headers = self._fill_common_headers(txid=txid, client_tags=client_tags)
1071
1121
 
1122
+ if arrow_schema is None:
1123
+ arrow_schema = pa.schema([])
1124
+
1072
1125
  serialized_schema = arrow_schema.serialize()
1073
1126
  headers['Content-Length'] = str(len(serialized_schema))
1074
1127
  if use_external_row_ids_allocation:
@@ -1078,6 +1131,11 @@ class VastdbApi:
1078
1131
  if create_imports_table:
1079
1132
  url_params['sub-table'] = IMPORTED_OBJECTS_TABLE_NAME
1080
1133
 
1134
+ if topic_partitions > 0:
1135
+ table_props = _encode_table_props(message_timestamp_type=message_timestamp_type, retention_ms=retention_ms, message_timestamp_after_max_ms=message_timestamp_after_max_ms, message_timestamp_before_max_ms=message_timestamp_before_max_ms)
1136
+ if table_props is not None:
1137
+ url_params['table-props'] = table_props
1138
+
1081
1139
  self._request(
1082
1140
  method="POST",
1083
1141
  url=self._url(bucket=bucket, schema=schema, table=name, command="table", url_params=url_params),
@@ -1106,7 +1164,8 @@ class VastdbApi:
1106
1164
  return TableStatsResult(num_rows, size_in_bytes, is_external_rowid_alloc, tuple(endpoints))
1107
1165
 
1108
1166
  def alter_table(self, bucket, schema, name, txid=0, client_tags=[], table_properties="",
1109
- new_name="", expected_retvals=[]):
1167
+ new_name="", expected_retvals=[],
1168
+ message_timestamp_type=None, retention_ms=None, message_timestamp_after_max_ms=None, message_timestamp_before_max_ms=None):
1110
1169
  """
1111
1170
  PUT /mybucket/myschema/mytable?table HTTP/1.1
1112
1171
  Content-Length: ContentLength
@@ -1118,6 +1177,11 @@ class VastdbApi:
1118
1177
  """
1119
1178
  builder = flatbuffers.Builder(1024)
1120
1179
 
1180
+ if message_timestamp_type is not None or retention_ms is not None or message_timestamp_after_max_ms is not None or message_timestamp_before_max_ms is not None:
1181
+ table_properties = _encode_table_props(message_timestamp_type=message_timestamp_type, retention_ms=retention_ms, message_timestamp_after_max_ms=message_timestamp_after_max_ms, message_timestamp_before_max_ms=message_timestamp_before_max_ms)
1182
+ if table_properties is None:
1183
+ table_properties = ""
1184
+
1121
1185
  properties = builder.CreateString(table_properties)
1122
1186
  tabular_alter_table.Start(builder)
1123
1187
  if len(table_properties):
vastdb/schema.py CHANGED
@@ -77,7 +77,11 @@ class Schema:
77
77
  return result
78
78
 
79
79
  def create_table(self, table_name: str, columns: pa.Schema, fail_if_exists=True, use_external_row_ids_allocation=False) -> "Table":
80
- """Create a new table under this schema."""
80
+ """Create a new table under this schema.
81
+
82
+ A virtual `vastdb_rowid` column (of `int64` type) can be created to access and filter by internal VAST row IDs.
83
+ See https://support.vastdata.com/s/article/UUID-48d0a8cf-5786-5ef3-3fa3-9c64e63a0967 for more details.
84
+ """
81
85
  if current := self.table(table_name, fail_if_missing=False):
82
86
  if fail_if_exists:
83
87
  raise errors.TableExists(self.bucket.name, self.name, table_name)
@@ -142,5 +146,4 @@ class Schema:
142
146
 
143
147
 
144
148
  def _parse_table_info(table_info, schema: "schema.Schema"):
145
- stats = table.TableStats(num_rows=table_info.num_rows, size_in_bytes=table_info.size_in_bytes)
146
- return table.Table(name=table_info.name, schema=schema, handle=int(table_info.handle), stats=stats, _imports_table=False)
149
+ return table.Table(name=table_info.name, schema=schema, handle=int(table_info.handle), _imports_table=False)
vastdb/table.py CHANGED
@@ -112,7 +112,6 @@ class Table:
112
112
  name: str
113
113
  schema: "schema.Schema"
114
114
  handle: int
115
- stats: TableStats
116
115
  arrow_schema: pa.Schema = field(init=False, compare=False, repr=False)
117
116
  _ibis_table: ibis.Schema = field(init=False, compare=False, repr=False)
118
117
  _imports_table: bool
@@ -139,6 +138,11 @@ class Table:
139
138
  """Return bucket."""
140
139
  return self.schema.bucket
141
140
 
141
+ @property
142
+ def stats(self):
143
+ """Fetch table's statistics from server."""
144
+ return self.get_stats()
145
+
142
146
  def columns(self) -> pa.Schema:
143
147
  """Return columns' metadata."""
144
148
  fields = []
@@ -576,7 +580,7 @@ class Table:
576
580
  def imports_table(self) -> Optional["Table"]:
577
581
  """Get the imports table of this table."""
578
582
  self.tx._rpc.features.check_imports_table()
579
- return Table(name=self.name, schema=self.schema, handle=int(self.handle), stats=self.stats, _imports_table=True)
583
+ return Table(name=self.name, schema=self.schema, handle=int(self.handle), _imports_table=True)
580
584
 
581
585
  def __getitem__(self, col_name):
582
586
  """Allow constructing ibis-like column expressions from this table.
@@ -7,6 +7,7 @@ from itertools import cycle
7
7
  import pytest
8
8
 
9
9
  import vastdb.errors
10
+ from vastdb._internal import UnsupportedServer
10
11
 
11
12
  log = logging.getLogger(__name__)
12
13
 
@@ -32,13 +33,14 @@ def test_bad_endpoint(session):
32
33
  def test_version_extraction():
33
34
  # A list of version and expected version parsed by API
34
35
  TEST_CASES = [
35
- (None, None), # vast server without version in header
36
- ("5", None), # major
37
- ("5.2", None), # major.minor
38
- ("5.2.0", None), # major.minor.patch
39
- ("5.2.0.10", (5, 2, 0, 10)), # major.minor.patch.protocol
40
- ("5.2.0.10 some other things", None), # suffix
41
- ("5.2.0.10.20", None), # extra version
36
+ ("nginx", UnsupportedServer), # non-vast server
37
+ ("vast", NotImplementedError), # vast server without version in header
38
+ ("vast 5", NotImplementedError), # major
39
+ ("vast 5.2", NotImplementedError), # major.minor
40
+ ("vast 5.2.0", NotImplementedError), # major.minor.patch
41
+ ("vast 5.2.0.10", (5, 2, 0, 10)), # major.minor.patch.protocol
42
+ ("vast 5.2.0.10 some other things", NotImplementedError), # suffix
43
+ ("vast 5.2.0.10.20", NotImplementedError), # extra version
42
44
  ]
43
45
 
44
46
  # Mock OPTIONS handle that cycles through the test cases response
@@ -53,8 +55,7 @@ def test_version_extraction():
53
55
  self.end_headers()
54
56
 
55
57
  def version_string(self):
56
- version = next(self.versions_iterator)[0]
57
- return f"vast {version}" if version else "vast"
58
+ return next(self.versions_iterator)[0]
58
59
 
59
60
  def log_message(self, format, *args):
60
61
  log.debug(format, *args)
@@ -74,7 +75,10 @@ def test_version_extraction():
74
75
 
75
76
  try:
76
77
  for _, expected in TEST_CASES:
77
- with (pytest.raises(NotImplementedError) if expected is None else contextlib.nullcontext()):
78
+ manager = contextlib.nullcontext()
79
+ if isinstance(expected, type) and issubclass(expected, NotImplementedError):
80
+ manager = pytest.raises(expected)
81
+ with manager:
78
82
  s = vastdb.connect(endpoint=f"http://localhost:{httpd.server_port}", access="abc", secret="abc")
79
83
  assert s.api.vast_version == expected
80
84
  finally:
@@ -452,8 +452,8 @@ def test_filters(session, clean_bucket_name):
452
452
  assert select((t['a'] > 111) | (t['a'] < 333) | (t['a'] == 777)) == expected.filter((pc.field('a') > 111) | (pc.field('a') < 333) | (pc.field('a') == 777))
453
453
 
454
454
  assert select(t['s'].isnull()) == expected.filter(pc.field('s').is_null())
455
- assert select((t['s'].isnull()) | (t['s'] == 'bb')) == expected.filter((pc.field('s').is_null()) | (pc.field('s') == 'bb'))
456
- assert select((t['s'].isnull()) & (t['b'] == 3.5)) == expected.filter((pc.field('s').is_null()) & (pc.field('b') == 3.5))
455
+ assert select((t['s'].isnull()) | (t['s'] == 'bb')) == expected.filter((pc.field('s').is_null()) | (pc.field('s') == 'bb'))
456
+ assert select((t['s'].isnull()) & (t['b'] == 3.5)) == expected.filter((pc.field('s').is_null()) & (pc.field('b') == 3.5))
457
457
 
458
458
  assert select(~t['s'].isnull()) == expected.filter(~pc.field('s').is_null())
459
459
  assert select(t['s'].contains('b')) == expected.filter(pc.field('s') == 'bb')
@@ -882,3 +882,84 @@ def test_starts_with(session, clean_bucket_name):
882
882
 
883
883
  res = table.select(predicate=(table['s'].startswith('ab')) & (table['i'] > 3)).read_all()
884
884
  assert res.to_pydict() == {'i': [4], 's': ['abd']}
885
+
886
+
887
+ def test_external_row_id(session, clean_bucket_name):
888
+ columns = [
889
+ ('vastdb_rowid', pa.int64()),
890
+ ('x', pa.float32()),
891
+ ('y', pa.utf8()),
892
+ ]
893
+
894
+ with session.transaction() as tx:
895
+ s = tx.bucket(clean_bucket_name).create_schema('s')
896
+
897
+ t = s.create_table('t1', pa.schema(columns))
898
+ assert not t.stats.is_external_rowid_alloc
899
+ t.insert(pa.record_batch(schema=pa.schema(columns), data=[[0], [1.5], ['ABC']]))
900
+ assert t.stats.is_external_rowid_alloc
901
+
902
+ t = s.create_table('t2', pa.schema(columns))
903
+ assert not t.stats.is_external_rowid_alloc
904
+ t.insert(pa.record_batch(schema=pa.schema(columns[1:]), data=[[1.5], ['ABC']]))
905
+ assert not t.stats.is_external_rowid_alloc
906
+
907
+
908
+ def test_multiple_contains_clauses(session, clean_bucket_name):
909
+ columns = pa.schema([
910
+ ('theint', pa.int32()),
911
+ ('thestring', pa.string()),
912
+ ('theotherstring', pa.string()),
913
+ ])
914
+
915
+ expected = pa.table(schema=columns, data=[
916
+ [111, 222, 333, 444, 555],
917
+ ['abc', 'efg', 'hij', 'klm', 'nop'],
918
+ ['abcd', 'bcde', 'cdef', 'defg', 'efgh'],
919
+ ])
920
+ with (prepare_data(session, clean_bucket_name, 's', 't', expected) as t):
921
+ failed_preds = [
922
+ lambda t: (t["thestring"].contains("b") | t["theotherstring"].contains("a")),
923
+ lambda t: (~t["thestring"].contains("a")),
924
+ ]
925
+
926
+ assert t.select(predicate=t["thestring"].contains("b")).read_all() == expected.filter(pc.match_substring(expected["thestring"], "b"))
927
+ assert (t.select(predicate=(t["thestring"].contains("b")) & (t["theotherstring"].startswith("a"))).read_all() ==
928
+ expected.filter(pc.and_(
929
+ pc.match_substring(expected["thestring"], "b"),
930
+ pc.starts_with(expected["thestring"], "a")
931
+ )))
932
+ assert (t.select(predicate=(t["thestring"].contains("b") & (t["thestring"].contains("y")))).read_all() ==
933
+ t.select(predicate=t["thestring"].contains("y") & (t["thestring"].contains("b"))).read_all())
934
+
935
+ assert (t.select(predicate=(t["thestring"].contains("o") & (t["theint"] > 500))).read_all() ==
936
+ pc.filter(expected, pc.and_(
937
+ pc.match_substring(expected["thestring"], "o"),
938
+ pc.greater(expected["theint"], 500)
939
+ )))
940
+ assert (t.select(predicate=((t["thestring"].contains("bc")) | (t["thestring"].contains("kl")) |
941
+ (t["thestring"] == "hi") | (t["thestring"].startswith("e")))).read_all() ==
942
+ expected.filter(
943
+ pc.or_(pc.or_(pc.match_substring(expected["thestring"], "bc"),
944
+ pc.match_substring(expected["thestring"], "kl")),
945
+ pc.or_(pc.equal(expected["thestring"], "hi"),
946
+ pc.starts_with(expected["thestring"], "e"))
947
+ )))
948
+ assert (t.select(predicate=((t["thestring"].contains("abc")) | (t["thestring"].contains("xyz")) |
949
+ (t["thestring"].startswith("z")) | (t["thestring"].isnull()))).read_all() ==
950
+ pc.filter(expected,
951
+ pc.or_(pc.or_(pc.match_substring(expected["thestring"], "abc"),
952
+ pc.match_substring(expected["thestring"], "xyz")),
953
+ pc.or_(pc.starts_with(expected["thestring"], "z"),
954
+ pc.is_null(expected["thestring"]))
955
+ )))
956
+ assert (t.select(predicate=((t["thestring"].contains("k")) & (t["theotherstring"].contains("h")) &
957
+ (t["theint"] > 500))).read_all() ==
958
+ pc.filter(expected, pc.and_(
959
+ pc.and_(pc.match_substring(expected["thestring"], "k"),
960
+ pc.match_substring(expected["theotherstring"], "h")),
961
+ pc.greater(expected["theint"], 500)
962
+ )))
963
+ for pred in failed_preds:
964
+ with pytest.raises(NotImplementedError):
965
+ t.select(predicate=pred(t)).read_all()
@@ -72,20 +72,31 @@ def test_ndu_while_querying(session, test_bucket_name, schema_name, table_name):
72
72
  """
73
73
  Executing queries while a NDU takes place.
74
74
  """
75
- # TODO: Before merging run mypy and print query result
76
-
75
+ amount_of_queries_in_parallel = 5
77
76
  config = QueryConfig(num_splits=1, num_sub_splits=1)
78
-
79
77
  logger.info(f'{test_bucket_name=}, {schema_name=}, {table_name=}')
80
78
 
81
- for query in range(300):
79
+ seed = random.randint(0, 100)
80
+ logger.info(f'{seed=}')
81
+ r = random.Random(seed)
82
+
83
+ def _execute_single_query(expected=None):
84
+ time.sleep(r.random())
82
85
  with session.transaction() as tx:
83
86
  t = tx.bucket(test_bucket_name).schema(schema_name).table(table_name)
84
87
  s = time.time()
85
- if query == 0:
86
- res = t.select(config=config).read_all()
87
- logger.info(f'{res=}')
88
- else:
89
- assert res == t.select(config=config).read_all()
88
+ res = t.select(config=config).read_all()
90
89
  e = time.time()
91
- logger.info(f'{query=} took {e - s}')
90
+ if expected:
91
+ assert expected == res
92
+ logger.info(f'query took {e - s}')
93
+ return res
94
+
95
+ initial_res = _execute_single_query()
96
+ for i in range(300):
97
+ with ThreadPoolExecutor() as executor:
98
+ futures = [executor.submit(_execute_single_query, initial_res) for _ in range(amount_of_queries_in_parallel)]
99
+ for future in futures:
100
+ future.result()
101
+
102
+ logger.info(f"finished running {amount_of_queries_in_parallel} queries, loop {i}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vastdb
3
- Version: 1.3.1
3
+ Version: 1.3.3
4
4
  Summary: VAST Data SDK
5
5
  Home-page: https://github.com/vast-data/vastdb_sdk
6
6
  Author: VAST DATA
@@ -138,26 +138,28 @@ vast_flatbuf/tabular/Column.py,sha256=2iXTlN8pynb_Sf7n15k8n2uXP_8eoz2RqKrUbTBrIy
138
138
  vast_flatbuf/tabular/ColumnType.py,sha256=_4-jMG08VR2zdn1ZH7F4aahYPxWsBSm7adUoVf-HFSU,151
139
139
  vast_flatbuf/tabular/CreateProjectionRequest.py,sha256=POlK1DrYMAldNJscLIRL3j4jAT0Sv_fRzfvBXwZAAMw,2516
140
140
  vast_flatbuf/tabular/CreateSchemaRequest.py,sha256=MrOfWaFu0Q1-mxLlGV8YMPajZ5kASyvowVSrKU-NPx8,1626
141
+ vast_flatbuf/tabular/CreateViewRequest.py,sha256=zagEVFrcWZLDY2exRHxr_uf-Yirchp7uYLk5b6Zvah8,4497
141
142
  vast_flatbuf/tabular/GetProjectionTableStatsResponse.py,sha256=Bp-ln-0lcZEiUvp3vWYmnCP6t2UsZ5J-lezgkUUWhzo,3474
142
143
  vast_flatbuf/tabular/GetTableStatsResponse.py,sha256=_UsKj6-VAvyDZ8Eku9fegQlRKV-T_0Dsb7qjulYoZus,4655
143
144
  vast_flatbuf/tabular/ImportDataRequest.py,sha256=f1chKp5d5NUxfNjI8YI1o4MYInF8UDhIhpWkT3vG4Do,2450
144
145
  vast_flatbuf/tabular/ListProjectionsResponse.py,sha256=secqrBsJY3ydbA28j09rmxzBqj-c1JNqaP7JMuib7nE,4240
145
146
  vast_flatbuf/tabular/ListSchemasResponse.py,sha256=V8tbwcWAC96eNwuoqDNqCSb02BnMdq60TpyISuWTVMk,3036
146
147
  vast_flatbuf/tabular/ListTablesResponse.py,sha256=V7jZAS8ryKY8s6o_QyjWzgan-rsGm17zjKEmi7K6qTM,3550
147
- vast_flatbuf/tabular/ObjectDetails.py,sha256=qW0WtbkCYYE_L-Kw6VNRDCLYaRm5lKvTbLNkfD4zV4A,3589
148
+ vast_flatbuf/tabular/ListViewsResponse.py,sha256=tn7mfW9afjWxMyBs3tsBcS_JA7uTAVNkFyaM7tctyns,3521
149
+ vast_flatbuf/tabular/ObjectDetails.py,sha256=yS16tYAu8BCYe7h_B4w7C9BDRVVxFMFg8_mkCyU7nOs,4697
148
150
  vast_flatbuf/tabular/S3File.py,sha256=KC9c2oS5-JXwTTriUVFdjOvRG0B54Cq9kviSDZY3NI0,4450
149
151
  vast_flatbuf/tabular/VipRange.py,sha256=_BJd1RRZAcK76T9vlsHzXKYVsPVaz6WTEAqStMQCAUQ,2069
150
152
  vast_flatbuf/tabular/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
151
153
  vastdb/__init__.py,sha256=J1JjKiFkKC95BHowfh9kJfQFTjRce-QMsc6zF_FfxC0,432
152
- vastdb/_internal.py,sha256=LmiiNmUqJFfNGUoe362sHwU8M3aKbN1VWYIvobrgeK8,92322
154
+ vastdb/_internal.py,sha256=nLa2G6FwjPiirjXo4HBc3Cs6P2XH8ub1yBsy1Zxu0R8,95257
153
155
  vastdb/bucket.py,sha256=5KuKhPjZOevznZqWHDVVocejvAy7dcwobPuV6BJCfPc,2544
154
156
  vastdb/config.py,sha256=1tMYtzKXerGcIUjH4tIGEvZNWvO4fviCEdcNCnELJZo,2269
155
157
  vastdb/conftest.py,sha256=X2kVveySPQYZlVBXUMoo7Oea5IsvmJzjdqq3fpH2kVw,3469
156
158
  vastdb/errors.py,sha256=2XR1ko7J5nkfiHSAgwuVAADw0SsyqxOwSeFaGgKZEXM,4186
157
159
  vastdb/features.py,sha256=DxV746LSkORwVSD6MP2hdXRfnyoLkJwtOwGmp1dnquo,1322
158
- vastdb/schema.py,sha256=IaZDJsx0ms_dJVXeyCcSD8Dt3TNJkqR3739XOnDBM_E,6177
160
+ vastdb/schema.py,sha256=N9GLEoSFPrbpreJbCwcWGCtknoNQkavqra8UQvCzy3E,6306
159
161
  vastdb/session.py,sha256=toMR0BXwTaECdWDKnIZky1F3MA1SmelRBiqCrqQ3GCM,2067
160
- vastdb/table.py,sha256=C6Zz0zolRRRbf5EQBvDRAofl3kGvfz4PjZwGeQongTI,31106
162
+ vastdb/table.py,sha256=PHtirFNL8j9PlXAgJp_rcLU37FgnWj0dPaFPFYWm28o,31186
161
163
  vastdb/transaction.py,sha256=NlVkEowJ_pmtffjWBBDaKExYDKPekjSZyj_fK_bZPJE,3026
162
164
  vastdb/util.py,sha256=8CUnVRsJukC3uNHNoB5D0qPf0FxS8OSdVB84nNoLJKc,6290
163
165
  vastdb/bench/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -194,16 +196,16 @@ vastdb/tests/test_duckdb.py,sha256=STw_1PwTQR8Naz6s0p6lQTV1ZTKKhe3LPBUbhqzTCu0,1
194
196
  vastdb/tests/test_imports.py,sha256=xKub3-bisFjH0BsZM8COfiUWuMrtoOoQKprF6VQT9RI,5669
195
197
  vastdb/tests/test_nested.py,sha256=LPU6uV3Ri23dBzAEMFQqRPbqapV5LfmiHSHkhILPIY0,6332
196
198
  vastdb/tests/test_projections.py,sha256=3y1kubwVrzO-xoR0hyps7zrjOJI8niCYspaFTN16Q9w,4540
197
- vastdb/tests/test_sanity.py,sha256=oiV2gb05aPyG5RMNUQZlyjNlg3T7Fig1_8OJzpAgcsk,3038
199
+ vastdb/tests/test_sanity.py,sha256=bv1ypGDzvOgmMvGbucDYiLQu8krQLlE6NB3M__q87x8,3303
198
200
  vastdb/tests/test_schemas.py,sha256=l70YQMlx2UL1KRQhApriiG2ZM7GJF-IzWU31H3Yqn1U,3312
199
- vastdb/tests/test_tables.py,sha256=17-t9VkEJRIW43Yf-lwEI7jHn8teOJvv-eZgANcvTkM,35023
201
+ vastdb/tests/test_tables.py,sha256=711oTcdw431zs5LAyDxvTaJqF86xYwVCo5iZxSpH58o,39184
200
202
  vastdb/tests/test_util.py,sha256=n7gvT5Wg6b6bxgqkFXkYqvFd_W1GlUdVfmPv66XYXyA,1956
201
203
  vastdb/tests/util.py,sha256=dpRJYbboDnlqL4qIdvScpp8--5fxRUBIcIYitrfcj9o,555
202
204
  vastdb/vast_tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
203
205
  vastdb/vast_tests/test_ha.py,sha256=744P4G6VJ09RIkHhMQL4wlipCBJWQVMhyvUrSc4k1HQ,975
204
- vastdb/vast_tests/test_scale.py,sha256=yPF5sL2X7fiP_QooV3OnZAzsW38ANfkHFXq8B1_Uh-s,3515
205
- vastdb-1.3.1.dist-info/LICENSE,sha256=obffan7LYrq7hLHNrY7vHcn2pKUTBUYXMKu-VOAvDxU,11333
206
- vastdb-1.3.1.dist-info/METADATA,sha256=oyVkT5bnbEKiuHPfiYuQKtu0C1N7yfeQTSOnhRWpF8c,1340
207
- vastdb-1.3.1.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
208
- vastdb-1.3.1.dist-info/top_level.txt,sha256=Vsj2MKtlhPg0J4so64slQtnwjhgoPmJgcG-6YcVAwVc,20
209
- vastdb-1.3.1.dist-info/RECORD,,
206
+ vastdb/vast_tests/test_scale.py,sha256=5jGwOdZH6Tv5tPdZYPWoqcxOceI2jA5i2D1zNKZHER4,3958
207
+ vastdb-1.3.3.dist-info/LICENSE,sha256=obffan7LYrq7hLHNrY7vHcn2pKUTBUYXMKu-VOAvDxU,11333
208
+ vastdb-1.3.3.dist-info/METADATA,sha256=xqBDm4Puvz348pzEDM7b_IIs-nZDSha7Sgg0RHHh94w,1340
209
+ vastdb-1.3.3.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
210
+ vastdb-1.3.3.dist-info/top_level.txt,sha256=Vsj2MKtlhPg0J4so64slQtnwjhgoPmJgcG-6YcVAwVc,20
211
+ vastdb-1.3.3.dist-info/RECORD,,
File without changes