pixeltable 0.3.6__py3-none-any.whl → 0.3.8__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.
Potentially problematic release.
This version of pixeltable might be problematic. Click here for more details.
- pixeltable/__init__.py +5 -3
- pixeltable/__version__.py +2 -2
- pixeltable/catalog/__init__.py +1 -0
- pixeltable/catalog/catalog.py +335 -128
- pixeltable/catalog/column.py +22 -5
- pixeltable/catalog/dir.py +19 -6
- pixeltable/catalog/insertable_table.py +34 -37
- pixeltable/catalog/named_function.py +0 -4
- pixeltable/catalog/schema_object.py +28 -42
- pixeltable/catalog/table.py +193 -158
- pixeltable/catalog/table_version.py +191 -232
- pixeltable/catalog/table_version_handle.py +50 -0
- pixeltable/catalog/table_version_path.py +49 -33
- pixeltable/catalog/view.py +56 -96
- pixeltable/config.py +103 -0
- pixeltable/dataframe.py +89 -89
- pixeltable/env.py +98 -168
- pixeltable/exec/aggregation_node.py +5 -4
- pixeltable/exec/cache_prefetch_node.py +1 -1
- pixeltable/exec/component_iteration_node.py +13 -9
- pixeltable/exec/data_row_batch.py +3 -3
- pixeltable/exec/exec_context.py +0 -4
- pixeltable/exec/exec_node.py +3 -2
- pixeltable/exec/expr_eval/schedulers.py +2 -1
- pixeltable/exec/in_memory_data_node.py +9 -4
- pixeltable/exec/row_update_node.py +1 -2
- pixeltable/exec/sql_node.py +20 -16
- pixeltable/exprs/__init__.py +2 -0
- pixeltable/exprs/arithmetic_expr.py +7 -11
- pixeltable/exprs/array_slice.py +1 -1
- pixeltable/exprs/column_property_ref.py +3 -3
- pixeltable/exprs/column_ref.py +12 -13
- pixeltable/exprs/comparison.py +3 -6
- pixeltable/exprs/compound_predicate.py +4 -4
- pixeltable/exprs/expr.py +31 -22
- pixeltable/exprs/expr_dict.py +3 -3
- pixeltable/exprs/expr_set.py +1 -1
- pixeltable/exprs/function_call.py +110 -80
- pixeltable/exprs/globals.py +3 -3
- pixeltable/exprs/in_predicate.py +1 -1
- pixeltable/exprs/inline_expr.py +3 -3
- pixeltable/exprs/is_null.py +1 -1
- pixeltable/exprs/json_mapper.py +2 -2
- pixeltable/exprs/json_path.py +17 -10
- pixeltable/exprs/literal.py +1 -1
- pixeltable/exprs/method_ref.py +2 -2
- pixeltable/exprs/row_builder.py +8 -17
- pixeltable/exprs/rowid_ref.py +21 -10
- pixeltable/exprs/similarity_expr.py +5 -5
- pixeltable/exprs/sql_element_cache.py +1 -1
- pixeltable/exprs/type_cast.py +2 -3
- pixeltable/exprs/variable.py +2 -2
- pixeltable/ext/__init__.py +2 -0
- pixeltable/ext/functions/__init__.py +2 -0
- pixeltable/ext/functions/yolox.py +3 -3
- pixeltable/func/__init__.py +3 -1
- pixeltable/func/aggregate_function.py +9 -9
- pixeltable/func/callable_function.py +3 -4
- pixeltable/func/expr_template_function.py +6 -16
- pixeltable/func/function.py +48 -14
- pixeltable/func/function_registry.py +1 -3
- pixeltable/func/query_template_function.py +5 -12
- pixeltable/func/signature.py +23 -22
- pixeltable/func/tools.py +3 -3
- pixeltable/func/udf.py +6 -4
- pixeltable/functions/__init__.py +2 -0
- pixeltable/functions/fireworks.py +7 -4
- pixeltable/functions/globals.py +4 -5
- pixeltable/functions/huggingface.py +1 -5
- pixeltable/functions/image.py +17 -7
- pixeltable/functions/llama_cpp.py +1 -1
- pixeltable/functions/mistralai.py +1 -1
- pixeltable/functions/ollama.py +4 -4
- pixeltable/functions/openai.py +19 -19
- pixeltable/functions/string.py +23 -30
- pixeltable/functions/timestamp.py +11 -6
- pixeltable/functions/together.py +14 -12
- pixeltable/functions/util.py +1 -1
- pixeltable/functions/video.py +5 -4
- pixeltable/functions/vision.py +6 -9
- pixeltable/functions/whisper.py +3 -3
- pixeltable/globals.py +246 -260
- pixeltable/index/__init__.py +2 -0
- pixeltable/index/base.py +1 -1
- pixeltable/index/btree.py +3 -1
- pixeltable/index/embedding_index.py +11 -5
- pixeltable/io/external_store.py +11 -12
- pixeltable/io/label_studio.py +4 -3
- pixeltable/io/parquet.py +57 -56
- pixeltable/iterators/__init__.py +4 -2
- pixeltable/iterators/audio.py +11 -11
- pixeltable/iterators/document.py +10 -10
- pixeltable/iterators/string.py +1 -2
- pixeltable/iterators/video.py +14 -15
- pixeltable/metadata/__init__.py +9 -5
- pixeltable/metadata/converters/convert_10.py +0 -1
- pixeltable/metadata/converters/convert_15.py +0 -2
- pixeltable/metadata/converters/convert_23.py +0 -2
- pixeltable/metadata/converters/convert_24.py +3 -3
- pixeltable/metadata/converters/convert_25.py +1 -1
- pixeltable/metadata/converters/convert_27.py +0 -2
- pixeltable/metadata/converters/convert_28.py +0 -2
- pixeltable/metadata/converters/convert_29.py +7 -8
- pixeltable/metadata/converters/util.py +7 -7
- pixeltable/metadata/schema.py +27 -19
- pixeltable/plan.py +68 -40
- pixeltable/share/__init__.py +2 -0
- pixeltable/share/packager.py +15 -12
- pixeltable/share/publish.py +3 -5
- pixeltable/store.py +37 -38
- pixeltable/type_system.py +41 -28
- pixeltable/utils/coco.py +4 -4
- pixeltable/utils/console_output.py +1 -3
- pixeltable/utils/description_helper.py +1 -1
- pixeltable/utils/documents.py +3 -3
- pixeltable/utils/filecache.py +20 -9
- pixeltable/utils/formatter.py +2 -3
- pixeltable/utils/media_store.py +1 -1
- pixeltable/utils/pytorch.py +1 -1
- pixeltable/utils/sql.py +4 -4
- pixeltable/utils/transactional_directory.py +2 -1
- {pixeltable-0.3.6.dist-info → pixeltable-0.3.8.dist-info}/METADATA +1 -1
- pixeltable-0.3.8.dist-info/RECORD +174 -0
- pixeltable-0.3.6.dist-info/RECORD +0 -172
- {pixeltable-0.3.6.dist-info → pixeltable-0.3.8.dist-info}/LICENSE +0 -0
- {pixeltable-0.3.6.dist-info → pixeltable-0.3.8.dist-info}/WHEEL +0 -0
- {pixeltable-0.3.6.dist-info → pixeltable-0.3.8.dist-info}/entry_points.txt +0 -0
|
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import dataclasses
|
|
4
4
|
import importlib
|
|
5
|
-
import inspect
|
|
6
5
|
import logging
|
|
7
6
|
import time
|
|
8
7
|
import uuid
|
|
@@ -11,12 +10,10 @@ from uuid import UUID
|
|
|
11
10
|
|
|
12
11
|
import jsonschema.exceptions
|
|
13
12
|
import sqlalchemy as sql
|
|
14
|
-
import sqlalchemy.orm as orm
|
|
15
13
|
|
|
16
14
|
import pixeltable as pxt
|
|
17
15
|
import pixeltable.exceptions as excs
|
|
18
16
|
import pixeltable.exprs as exprs
|
|
19
|
-
import pixeltable.func as func
|
|
20
17
|
import pixeltable.index as index
|
|
21
18
|
import pixeltable.type_system as ts
|
|
22
19
|
from pixeltable.env import Env
|
|
@@ -32,6 +29,8 @@ from .globals import _POS_COLUMN_NAME, _ROWID_COLUMN_NAME, MediaValidation, Upda
|
|
|
32
29
|
if TYPE_CHECKING:
|
|
33
30
|
from pixeltable import exec, store
|
|
34
31
|
|
|
32
|
+
from .table_version_handle import TableVersionHandle
|
|
33
|
+
|
|
35
34
|
_logger = logging.getLogger('pixeltable')
|
|
36
35
|
|
|
37
36
|
|
|
@@ -48,26 +47,27 @@ class TableVersion:
|
|
|
48
47
|
* TODO: create a separate hierarchy of objects that records the version-independent tree of tables/views, and
|
|
49
48
|
have TableVersions reference those
|
|
50
49
|
- mutable TableVersions record their TableVersionPath, which is needed for expr evaluation in updates
|
|
50
|
+
|
|
51
|
+
Instances of TableVersion should not be stored as member variables (ie, used across transaction boundaries).
|
|
52
|
+
Use a TableVersionHandle instead.
|
|
51
53
|
"""
|
|
52
54
|
|
|
53
55
|
id: UUID
|
|
54
56
|
name: str
|
|
57
|
+
effective_version: Optional[int]
|
|
55
58
|
version: int
|
|
56
59
|
comment: str
|
|
57
60
|
media_validation: MediaValidation
|
|
58
61
|
num_retained_versions: int
|
|
59
62
|
schema_version: int
|
|
60
63
|
view_md: Optional[schema.ViewMd]
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
effective_version: Optional[int]
|
|
64
|
-
path: Optional[pxt.catalog.TableVersionPath]
|
|
65
|
-
base: Optional[TableVersion]
|
|
64
|
+
path: Optional[pxt.catalog.TableVersionPath] # only set for live tables; needed to resolve computed cols
|
|
65
|
+
base: Optional[TableVersionHandle] # only set for views
|
|
66
66
|
next_col_id: int
|
|
67
67
|
next_idx_id: int
|
|
68
68
|
next_rowid: int
|
|
69
69
|
predicate: Optional[exprs.Expr]
|
|
70
|
-
mutable_views: list[
|
|
70
|
+
mutable_views: list[TableVersionHandle] # target for data operation propagation (only set for live tables)
|
|
71
71
|
iterator_cls: Optional[type[ComponentIterator]]
|
|
72
72
|
iterator_args: Optional[exprs.InlineDict]
|
|
73
73
|
num_iterator_cols: int
|
|
@@ -99,37 +99,37 @@ class TableVersion:
|
|
|
99
99
|
self,
|
|
100
100
|
id: UUID,
|
|
101
101
|
tbl_md: schema.TableMd,
|
|
102
|
-
|
|
102
|
+
effective_version: Optional[int],
|
|
103
103
|
schema_version_md: schema.TableSchemaVersionMd,
|
|
104
|
-
|
|
104
|
+
mutable_views: list[TableVersionHandle],
|
|
105
105
|
base_path: Optional[pxt.catalog.TableVersionPath] = None,
|
|
106
|
-
|
|
106
|
+
base: Optional[TableVersionHandle] = None,
|
|
107
|
+
# base_store_tbl: Optional['store.StoreBase'] = None,
|
|
107
108
|
):
|
|
108
|
-
# only one of base and base_path can be non-None
|
|
109
|
-
assert base is None or base_path is None
|
|
110
109
|
self.id = id
|
|
111
110
|
self.name = tbl_md.name
|
|
112
|
-
self.
|
|
111
|
+
self.effective_version = effective_version
|
|
112
|
+
self.version = tbl_md.current_version if effective_version is None else effective_version
|
|
113
113
|
self.comment = schema_version_md.comment
|
|
114
114
|
self.num_retained_versions = schema_version_md.num_retained_versions
|
|
115
115
|
self.schema_version = schema_version_md.schema_version
|
|
116
116
|
self.view_md = tbl_md.view_md # save this as-is, it's needed for _create_md()
|
|
117
|
-
is_view = tbl_md.view_md is not None
|
|
118
|
-
self.is_snapshot = (is_view and tbl_md.view_md.is_snapshot) or bool(is_snapshot)
|
|
119
|
-
self.include_base_columns = not is_view or tbl_md.view_md.include_base_columns
|
|
120
117
|
self.media_validation = MediaValidation[schema_version_md.media_validation.upper()]
|
|
121
|
-
|
|
122
|
-
self.
|
|
118
|
+
assert not (self.is_view and base is None)
|
|
119
|
+
self.base = base
|
|
123
120
|
|
|
124
121
|
# mutable tables need their TableVersionPath for expr eval during updates
|
|
122
|
+
from .table_version_handle import TableVersionHandle
|
|
125
123
|
from .table_version_path import TableVersionPath
|
|
126
124
|
|
|
127
125
|
if self.is_snapshot:
|
|
128
126
|
self.path = None
|
|
129
127
|
else:
|
|
130
|
-
|
|
128
|
+
self_handle = TableVersionHandle(id, self.effective_version)
|
|
129
|
+
if self.is_view:
|
|
130
|
+
assert base_path is not None
|
|
131
|
+
self.path = TableVersionPath(self_handle, base=base_path)
|
|
131
132
|
|
|
132
|
-
self.base = base_path.tbl_version if base_path is not None else base
|
|
133
133
|
if self.is_snapshot:
|
|
134
134
|
self.next_col_id = -1
|
|
135
135
|
self.next_idx_id = -1 # TODO: can snapshots have separate indices?
|
|
@@ -143,17 +143,15 @@ class TableVersion:
|
|
|
143
143
|
# view-specific initialization
|
|
144
144
|
from pixeltable import exprs
|
|
145
145
|
|
|
146
|
-
predicate_dict = None if
|
|
146
|
+
predicate_dict = None if self.view_md is None or self.view_md.predicate is None else self.view_md.predicate
|
|
147
147
|
self.predicate = exprs.Expr.from_dict(predicate_dict) if predicate_dict is not None else None
|
|
148
|
-
self.mutable_views =
|
|
149
|
-
if self.base is not None and not self.base.is_snapshot and not self.is_snapshot:
|
|
150
|
-
self.base.mutable_views.append(self)
|
|
148
|
+
self.mutable_views = mutable_views
|
|
151
149
|
|
|
152
150
|
# component view-specific initialization
|
|
153
151
|
self.iterator_cls = None
|
|
154
152
|
self.iterator_args = None
|
|
155
153
|
self.num_iterator_cols = 0
|
|
156
|
-
if
|
|
154
|
+
if self.view_md is not None and self.view_md.iterator_class_fqn is not None:
|
|
157
155
|
module_name, class_name = tbl_md.view_md.iterator_class_fqn.rsplit('.', 1)
|
|
158
156
|
module = importlib.import_module(module_name)
|
|
159
157
|
self.iterator_cls = getattr(module, class_name)
|
|
@@ -164,7 +162,7 @@ class TableVersion:
|
|
|
164
162
|
|
|
165
163
|
# register this table version now so that it's available when we're re-creating value exprs
|
|
166
164
|
cat = pxt.catalog.Catalog.get()
|
|
167
|
-
cat.
|
|
165
|
+
cat.add_tbl_version(self)
|
|
168
166
|
|
|
169
167
|
# init schema after we determined whether we're a component view, and before we create the store table
|
|
170
168
|
self.cols = []
|
|
@@ -179,42 +177,45 @@ class TableVersion:
|
|
|
179
177
|
# Init external stores (this needs to happen after the schema is created)
|
|
180
178
|
self._init_external_stores(tbl_md)
|
|
181
179
|
|
|
180
|
+
# Force column metadata to load, in order to surface any invalid metadata now (as warnings)
|
|
181
|
+
for col in self.cols_by_id.values():
|
|
182
|
+
_ = col.value_expr
|
|
183
|
+
|
|
182
184
|
def __hash__(self) -> int:
|
|
183
185
|
return hash(self.id)
|
|
184
186
|
|
|
185
|
-
def _get_column(self, tbl_id: UUID, col_id: int) -> Column:
|
|
186
|
-
if self.id == tbl_id:
|
|
187
|
-
return self.cols_by_id[col_id]
|
|
188
|
-
else:
|
|
189
|
-
if self.base is None:
|
|
190
|
-
raise excs.Error(f'Unknown table id: {tbl_id}')
|
|
191
|
-
return self.base._get_column(tbl_id, col_id)
|
|
192
|
-
|
|
193
187
|
def create_snapshot_copy(self) -> TableVersion:
|
|
194
188
|
"""Create a snapshot copy of this TableVersion"""
|
|
195
189
|
assert not self.is_snapshot
|
|
190
|
+
base = self.path.base.tbl_version if self.is_view else None
|
|
196
191
|
return TableVersion(
|
|
197
192
|
self.id,
|
|
198
193
|
self._create_tbl_md(),
|
|
199
194
|
self.version,
|
|
200
195
|
self._create_schema_version_md(preceding_schema_version=0), # preceding_schema_version: dummy value
|
|
201
|
-
|
|
202
|
-
base=
|
|
196
|
+
mutable_views=[],
|
|
197
|
+
base=base,
|
|
203
198
|
)
|
|
204
199
|
|
|
200
|
+
def create_handle(self) -> TableVersionHandle:
|
|
201
|
+
from .table_version_handle import TableVersionHandle
|
|
202
|
+
|
|
203
|
+
return TableVersionHandle(self.id, self.effective_version, tbl_version=self)
|
|
204
|
+
|
|
205
205
|
@classmethod
|
|
206
206
|
def create(
|
|
207
207
|
cls,
|
|
208
|
-
session: orm.Session,
|
|
209
208
|
dir_id: UUID,
|
|
210
209
|
name: str,
|
|
211
210
|
cols: list[Column],
|
|
212
211
|
num_retained_versions: int,
|
|
213
212
|
comment: str,
|
|
214
213
|
media_validation: MediaValidation,
|
|
215
|
-
base_path: Optional[pxt.catalog.TableVersionPath] = None,
|
|
214
|
+
# base_path: Optional[pxt.catalog.TableVersionPath] = None,
|
|
216
215
|
view_md: Optional[schema.ViewMd] = None,
|
|
217
216
|
) -> tuple[UUID, Optional[TableVersion]]:
|
|
217
|
+
session = Env.get().session
|
|
218
|
+
|
|
218
219
|
# assign ids
|
|
219
220
|
cols_by_name: dict[str, Column] = {}
|
|
220
221
|
for pos, col in enumerate(cols):
|
|
@@ -276,7 +277,7 @@ class TableVersion:
|
|
|
276
277
|
tbl_id=tbl_record.id, schema_version=0, md=dataclasses.asdict(schema_version_md)
|
|
277
278
|
)
|
|
278
279
|
|
|
279
|
-
# if this is purely a snapshot (it doesn't require any additional storage for columns and it
|
|
280
|
+
# if this is purely a snapshot (it doesn't require any additional storage for columns and it doesn't have a
|
|
280
281
|
# predicate to apply at runtime), we don't create a physical table and simply use the base's table version path
|
|
281
282
|
if view_md is not None and view_md.is_snapshot and view_md.predicate is None and len(cols) == 0:
|
|
282
283
|
session.add(tbl_record)
|
|
@@ -284,17 +285,20 @@ class TableVersion:
|
|
|
284
285
|
session.add(schema_version_record)
|
|
285
286
|
return tbl_record.id, None
|
|
286
287
|
|
|
287
|
-
assert (base_path is not None) == (view_md is not None)
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
288
|
+
# assert (base_path is not None) == (view_md is not None)
|
|
289
|
+
is_snapshot = view_md is not None and view_md.is_snapshot
|
|
290
|
+
effective_version = 0 if is_snapshot else None
|
|
291
|
+
base_path = pxt.catalog.TableVersionPath.from_md(view_md.base_versions) if view_md is not None else None
|
|
292
|
+
base = base_path.tbl_version if base_path is not None else None
|
|
293
|
+
tbl_version = cls(
|
|
294
|
+
tbl_record.id, table_md, effective_version, schema_version_md, [], base_path=base_path, base=base
|
|
295
|
+
)
|
|
291
296
|
|
|
292
|
-
|
|
293
|
-
tbl_version.store_tbl.create(conn)
|
|
297
|
+
tbl_version.store_tbl.create()
|
|
294
298
|
if view_md is None or not view_md.is_snapshot:
|
|
295
299
|
# add default indices, after creating the store table
|
|
296
300
|
for col in tbl_version.cols_by_name.values():
|
|
297
|
-
status = tbl_version._add_default_index(col
|
|
301
|
+
status = tbl_version._add_default_index(col)
|
|
298
302
|
assert status is None or status.num_excs == 0
|
|
299
303
|
|
|
300
304
|
# we re-create the tbl_record here, now that we have new index metadata
|
|
@@ -305,30 +309,30 @@ class TableVersion:
|
|
|
305
309
|
return tbl_record.id, tbl_version
|
|
306
310
|
|
|
307
311
|
@classmethod
|
|
308
|
-
def delete_md(cls, tbl_id: UUID
|
|
312
|
+
def delete_md(cls, tbl_id: UUID) -> None:
|
|
313
|
+
conn = Env.get().conn
|
|
309
314
|
conn.execute(sql.delete(schema.TableSchemaVersion.__table__).where(schema.TableSchemaVersion.tbl_id == tbl_id))
|
|
310
315
|
conn.execute(sql.delete(schema.TableVersion.__table__).where(schema.TableVersion.tbl_id == tbl_id))
|
|
311
316
|
conn.execute(sql.delete(schema.Table.__table__).where(schema.Table.id == tbl_id))
|
|
312
317
|
|
|
313
318
|
def drop(self) -> None:
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
self.store_tbl.drop(conn)
|
|
319
|
+
# delete this table and all associated data
|
|
320
|
+
MediaStore.delete(self.id)
|
|
321
|
+
FileCache.get().clear(tbl_id=self.id)
|
|
322
|
+
self.delete_md(self.id)
|
|
323
|
+
self.store_tbl.drop()
|
|
320
324
|
|
|
321
325
|
# de-register table version from catalog
|
|
322
326
|
from .catalog import Catalog
|
|
323
327
|
|
|
324
328
|
cat = Catalog.get()
|
|
325
|
-
|
|
326
|
-
# TODO: remove from tbl_dependents
|
|
329
|
+
cat.remove_tbl_version(self)
|
|
327
330
|
|
|
328
331
|
def _init_schema(self, tbl_md: schema.TableMd, schema_version_md: schema.TableSchemaVersionMd) -> None:
|
|
329
332
|
# create columns first, so the indices can reference them
|
|
330
333
|
self._init_cols(tbl_md, schema_version_md)
|
|
331
|
-
self.
|
|
334
|
+
if not self.is_snapshot:
|
|
335
|
+
self._init_idxs(tbl_md)
|
|
332
336
|
# create the sa schema only after creating the columns and indices
|
|
333
337
|
self._init_sa_schema()
|
|
334
338
|
|
|
@@ -356,7 +360,7 @@ class TableVersion:
|
|
|
356
360
|
schema_version_drop=col_md.schema_version_drop,
|
|
357
361
|
value_expr_dict=col_md.value_expr,
|
|
358
362
|
)
|
|
359
|
-
col.tbl = self
|
|
363
|
+
col.tbl = self.create_handle()
|
|
360
364
|
self.cols.append(col)
|
|
361
365
|
|
|
362
366
|
# populate the lookup structures before Expr.from_dict()
|
|
@@ -373,7 +377,6 @@ class TableVersion:
|
|
|
373
377
|
# make sure to traverse columns ordered by position = order in which cols were created;
|
|
374
378
|
# this guarantees that references always point backwards
|
|
375
379
|
if col_md.value_expr is not None:
|
|
376
|
-
refd_cols = exprs.Expr.get_refd_columns(col_md.value_expr)
|
|
377
380
|
self._record_refd_columns(col)
|
|
378
381
|
|
|
379
382
|
def _init_idxs(self, tbl_md: schema.TableMd) -> None:
|
|
@@ -393,7 +396,7 @@ class TableVersion:
|
|
|
393
396
|
# instantiate index object
|
|
394
397
|
cls_name = md.class_fqn.rsplit('.', 1)[-1]
|
|
395
398
|
cls = getattr(index_module, cls_name)
|
|
396
|
-
idx_col = self.
|
|
399
|
+
idx_col = self.path.get_column_by_id(UUID(md.indexed_col_tbl_id), md.indexed_col_id)
|
|
397
400
|
idx = cls.from_dict(idx_col, md.init_args)
|
|
398
401
|
|
|
399
402
|
# fix up the sa column type of the index value and undo columns
|
|
@@ -411,19 +414,15 @@ class TableVersion:
|
|
|
411
414
|
# need to record errors
|
|
412
415
|
from pixeltable.store import StoreComponentView, StoreTable, StoreView
|
|
413
416
|
|
|
414
|
-
if self.is_component_view
|
|
417
|
+
if self.is_component_view:
|
|
415
418
|
self.store_tbl = StoreComponentView(self)
|
|
416
|
-
elif self.is_view
|
|
419
|
+
elif self.is_view:
|
|
417
420
|
self.store_tbl = StoreView(self)
|
|
418
421
|
else:
|
|
419
422
|
self.store_tbl = StoreTable(self)
|
|
420
423
|
|
|
421
424
|
def _update_md(
|
|
422
|
-
self,
|
|
423
|
-
timestamp: float,
|
|
424
|
-
conn: sql.engine.Connection,
|
|
425
|
-
update_tbl_version: bool = True,
|
|
426
|
-
preceding_schema_version: Optional[int] = None,
|
|
425
|
+
self, timestamp: float, update_tbl_version: bool = True, preceding_schema_version: Optional[int] = None
|
|
427
426
|
) -> None:
|
|
428
427
|
"""Writes table metadata to the database.
|
|
429
428
|
|
|
@@ -436,6 +435,7 @@ class TableVersion:
|
|
|
436
435
|
"""
|
|
437
436
|
assert update_tbl_version or preceding_schema_version is None
|
|
438
437
|
|
|
438
|
+
conn = Env.get().conn
|
|
439
439
|
conn.execute(
|
|
440
440
|
sql.update(schema.Table.__table__)
|
|
441
441
|
.values({schema.Table.md: dataclasses.asdict(self._create_tbl_md())})
|
|
@@ -467,13 +467,12 @@ class TableVersion:
|
|
|
467
467
|
self.version += 1
|
|
468
468
|
preceding_schema_version = self.schema_version
|
|
469
469
|
self.schema_version = self.version
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
return status
|
|
470
|
+
status = self._add_index(col, idx_name, idx)
|
|
471
|
+
self._update_md(time.time(), preceding_schema_version=preceding_schema_version)
|
|
472
|
+
_logger.info(f'Added index {idx_name} on column {col.name} to table {self.name}')
|
|
473
|
+
return status
|
|
475
474
|
|
|
476
|
-
def _add_default_index(self, col: Column
|
|
475
|
+
def _add_default_index(self, col: Column) -> Optional[UpdateStatus]:
|
|
477
476
|
"""Add a B-tree index on this column if it has a compatible type"""
|
|
478
477
|
if not col.stored:
|
|
479
478
|
# if the column is intentionally not stored, we want to avoid the overhead of an index
|
|
@@ -487,12 +486,10 @@ class TableVersion:
|
|
|
487
486
|
if col.col_type.is_bool_type():
|
|
488
487
|
# B-trees on bools aren't useful
|
|
489
488
|
return None
|
|
490
|
-
status = self._add_index(col, idx_name=None, idx=index.BtreeIndex(col)
|
|
489
|
+
status = self._add_index(col, idx_name=None, idx=index.BtreeIndex(col))
|
|
491
490
|
return status
|
|
492
491
|
|
|
493
|
-
def _add_index(
|
|
494
|
-
self, col: Column, idx_name: Optional[str], idx: index.IndexBase, conn: sql.engine.Connection
|
|
495
|
-
) -> UpdateStatus:
|
|
492
|
+
def _add_index(self, col: Column, idx_name: Optional[str], idx: index.IndexBase) -> UpdateStatus:
|
|
496
493
|
assert not self.is_snapshot
|
|
497
494
|
idx_id = self.next_idx_id
|
|
498
495
|
self.next_idx_id += 1
|
|
@@ -513,7 +510,7 @@ class TableVersion:
|
|
|
513
510
|
schema_version_drop=None,
|
|
514
511
|
records_errors=idx.records_value_errors(),
|
|
515
512
|
)
|
|
516
|
-
val_col.tbl = self
|
|
513
|
+
val_col.tbl = self.create_handle()
|
|
517
514
|
val_col.col_type = val_col.col_type.copy(nullable=True)
|
|
518
515
|
self.next_col_id += 1
|
|
519
516
|
|
|
@@ -527,7 +524,7 @@ class TableVersion:
|
|
|
527
524
|
schema_version_drop=None,
|
|
528
525
|
records_errors=False,
|
|
529
526
|
)
|
|
530
|
-
undo_col.tbl = self
|
|
527
|
+
undo_col.tbl = self.create_handle()
|
|
531
528
|
undo_col.col_type = undo_col.col_type.copy(nullable=True)
|
|
532
529
|
self.next_col_id += 1
|
|
533
530
|
|
|
@@ -552,9 +549,9 @@ class TableVersion:
|
|
|
552
549
|
# add the columns and update the metadata
|
|
553
550
|
# TODO support on_error='abort' for indices; it's tricky because of the way metadata changes are entangled
|
|
554
551
|
# with the database operations
|
|
555
|
-
status = self._add_columns([val_col, undo_col],
|
|
552
|
+
status = self._add_columns([val_col, undo_col], print_stats=False, on_error='ignore')
|
|
556
553
|
# now create the index structure
|
|
557
|
-
idx.create_index(self._store_idx_name(idx_id), val_col
|
|
554
|
+
idx.create_index(self._store_idx_name(idx_id), val_col)
|
|
558
555
|
|
|
559
556
|
return status
|
|
560
557
|
|
|
@@ -575,10 +572,9 @@ class TableVersion:
|
|
|
575
572
|
del self.idxs_by_name[idx_md.name]
|
|
576
573
|
del self.idx_md[idx_id]
|
|
577
574
|
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
_logger.info(f'Dropped index {idx_md.name} on table {self.name}')
|
|
575
|
+
self._drop_columns([idx_info.val_col, idx_info.undo_col])
|
|
576
|
+
self._update_md(time.time(), preceding_schema_version=preceding_schema_version)
|
|
577
|
+
_logger.info(f'Dropped index {idx_md.name} on table {self.name}')
|
|
582
578
|
|
|
583
579
|
def add_columns(
|
|
584
580
|
self, cols: Iterable[Column], print_stats: bool, on_error: Literal['abort', 'ignore']
|
|
@@ -589,7 +585,7 @@ class TableVersion:
|
|
|
589
585
|
assert all(col.stored is not None for col in cols)
|
|
590
586
|
assert all(col.name not in self.cols_by_name for col in cols)
|
|
591
587
|
for col in cols:
|
|
592
|
-
col.tbl = self
|
|
588
|
+
col.tbl = self.create_handle()
|
|
593
589
|
col.id = self.next_col_id
|
|
594
590
|
self.next_col_id += 1
|
|
595
591
|
|
|
@@ -597,11 +593,10 @@ class TableVersion:
|
|
|
597
593
|
self.version += 1
|
|
598
594
|
preceding_schema_version = self.schema_version
|
|
599
595
|
self.schema_version = self.version
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
self._update_md(time.time(), conn, preceding_schema_version=preceding_schema_version)
|
|
596
|
+
status = self._add_columns(cols, print_stats=print_stats, on_error=on_error)
|
|
597
|
+
for col in cols:
|
|
598
|
+
_ = self._add_default_index(col)
|
|
599
|
+
self._update_md(time.time(), preceding_schema_version=preceding_schema_version)
|
|
605
600
|
_logger.info(f'Added columns {[col.name for col in cols]} to table {self.name}, new version: {self.version}')
|
|
606
601
|
|
|
607
602
|
msg = (
|
|
@@ -613,15 +608,11 @@ class TableVersion:
|
|
|
613
608
|
return status
|
|
614
609
|
|
|
615
610
|
def _add_columns(
|
|
616
|
-
self,
|
|
617
|
-
cols: Iterable[Column],
|
|
618
|
-
conn: sql.engine.Connection,
|
|
619
|
-
print_stats: bool,
|
|
620
|
-
on_error: Literal['abort', 'ignore'],
|
|
611
|
+
self, cols: Iterable[Column], print_stats: bool, on_error: Literal['abort', 'ignore']
|
|
621
612
|
) -> UpdateStatus:
|
|
622
613
|
"""Add and populate columns within the current transaction"""
|
|
623
614
|
cols = list(cols)
|
|
624
|
-
row_count = self.store_tbl.count(
|
|
615
|
+
row_count = self.store_tbl.count()
|
|
625
616
|
for col in cols:
|
|
626
617
|
if not col.col_type.nullable and not col.is_computed:
|
|
627
618
|
if row_count > 0:
|
|
@@ -644,7 +635,7 @@ class TableVersion:
|
|
|
644
635
|
self._record_refd_columns(col)
|
|
645
636
|
|
|
646
637
|
if col.is_stored:
|
|
647
|
-
self.store_tbl.add_column(col
|
|
638
|
+
self.store_tbl.add_column(col)
|
|
648
639
|
|
|
649
640
|
if not col.is_computed or not col.is_stored or row_count == 0:
|
|
650
641
|
continue
|
|
@@ -656,10 +647,9 @@ class TableVersion:
|
|
|
656
647
|
plan.ctx.num_rows = row_count
|
|
657
648
|
|
|
658
649
|
try:
|
|
659
|
-
plan.ctx.set_conn(conn)
|
|
660
650
|
plan.open()
|
|
661
651
|
try:
|
|
662
|
-
num_excs = self.store_tbl.load_column(col, plan, value_expr_slot_idx,
|
|
652
|
+
num_excs = self.store_tbl.load_column(col, plan, value_expr_slot_idx, on_error)
|
|
663
653
|
except sql.exc.DBAPIError as exc:
|
|
664
654
|
# Wrap the DBAPIError in an excs.Error to unify processing in the subsequent except block
|
|
665
655
|
raise excs.Error(f'SQL error during execution of computed column `{col.name}`:\n{exc}') from exc
|
|
@@ -687,12 +677,11 @@ class TableVersion:
|
|
|
687
677
|
num_rows=row_count,
|
|
688
678
|
num_computed_values=row_count,
|
|
689
679
|
num_excs=num_excs,
|
|
690
|
-
cols_with_excs=[f'{col.tbl.name}.{col.name}' for col in cols_with_excs if col.name is not None],
|
|
680
|
+
cols_with_excs=[f'{col.tbl.get().name}.{col.name}' for col in cols_with_excs if col.name is not None],
|
|
691
681
|
)
|
|
692
682
|
|
|
693
683
|
def drop_column(self, col: Column) -> None:
|
|
694
684
|
"""Drop a column from the table."""
|
|
695
|
-
from pixeltable.catalog import Catalog
|
|
696
685
|
|
|
697
686
|
assert not self.is_snapshot
|
|
698
687
|
|
|
@@ -701,23 +690,22 @@ class TableVersion:
|
|
|
701
690
|
preceding_schema_version = self.schema_version
|
|
702
691
|
self.schema_version = self.version
|
|
703
692
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
self._update_md(time.time(), conn, preceding_schema_version=preceding_schema_version)
|
|
693
|
+
# drop this column and all dependent index columns and indices
|
|
694
|
+
dropped_cols = [col]
|
|
695
|
+
dropped_idx_names: list[str] = []
|
|
696
|
+
for idx_info in self.idxs_by_name.values():
|
|
697
|
+
if idx_info.col != col:
|
|
698
|
+
continue
|
|
699
|
+
dropped_cols.extend([idx_info.val_col, idx_info.undo_col])
|
|
700
|
+
idx_md = self.idx_md[idx_info.id]
|
|
701
|
+
idx_md.schema_version_drop = self.schema_version
|
|
702
|
+
assert idx_md.name in self.idxs_by_name
|
|
703
|
+
dropped_idx_names.append(idx_md.name)
|
|
704
|
+
# update idxs_by_name
|
|
705
|
+
for idx_name in dropped_idx_names:
|
|
706
|
+
del self.idxs_by_name[idx_name]
|
|
707
|
+
self._drop_columns(dropped_cols)
|
|
708
|
+
self._update_md(time.time(), preceding_schema_version=preceding_schema_version)
|
|
721
709
|
_logger.info(f'Dropped column {col.name} from table {self.name}, new version: {self.version}')
|
|
722
710
|
|
|
723
711
|
def _drop_columns(self, cols: Iterable[Column]) -> None:
|
|
@@ -760,8 +748,7 @@ class TableVersion:
|
|
|
760
748
|
preceding_schema_version = self.schema_version
|
|
761
749
|
self.schema_version = self.version
|
|
762
750
|
|
|
763
|
-
|
|
764
|
-
self._update_md(time.time(), conn, preceding_schema_version=preceding_schema_version)
|
|
751
|
+
self._update_md(time.time(), preceding_schema_version=preceding_schema_version)
|
|
765
752
|
_logger.info(f'Renamed column {old_name} to {new_name} in table {self.name}, new version: {self.version}')
|
|
766
753
|
|
|
767
754
|
def set_comment(self, new_comment: Optional[str]):
|
|
@@ -781,15 +768,13 @@ class TableVersion:
|
|
|
781
768
|
self.version += 1
|
|
782
769
|
preceding_schema_version = self.schema_version
|
|
783
770
|
self.schema_version = self.version
|
|
784
|
-
|
|
785
|
-
self._update_md(time.time(), conn, preceding_schema_version=preceding_schema_version)
|
|
771
|
+
self._update_md(time.time(), preceding_schema_version=preceding_schema_version)
|
|
786
772
|
_logger.info(f'[{self.name}] Updating table schema to version: {self.version}')
|
|
787
773
|
|
|
788
774
|
def insert(
|
|
789
775
|
self,
|
|
790
776
|
rows: Optional[list[dict[str, Any]]],
|
|
791
777
|
df: Optional[pxt.DataFrame],
|
|
792
|
-
conn: Optional[sql.engine.Connection] = None,
|
|
793
778
|
print_stats: bool = False,
|
|
794
779
|
fail_on_exception: bool = True,
|
|
795
780
|
) -> UpdateStatus:
|
|
@@ -812,20 +797,11 @@ class TableVersion:
|
|
|
812
797
|
self.next_rowid += 1
|
|
813
798
|
yield rowid
|
|
814
799
|
|
|
815
|
-
|
|
816
|
-
with Env.get().engine.begin() as conn:
|
|
817
|
-
return self._insert(
|
|
818
|
-
plan, conn, time.time(), print_stats=print_stats, rowids=rowids(), abort_on_exc=fail_on_exception
|
|
819
|
-
)
|
|
820
|
-
else:
|
|
821
|
-
return self._insert(
|
|
822
|
-
plan, conn, time.time(), print_stats=print_stats, rowids=rowids(), abort_on_exc=fail_on_exception
|
|
823
|
-
)
|
|
800
|
+
return self._insert(plan, time.time(), print_stats=print_stats, rowids=rowids(), abort_on_exc=fail_on_exception)
|
|
824
801
|
|
|
825
802
|
def _insert(
|
|
826
803
|
self,
|
|
827
804
|
exec_plan: 'exec.ExecNode',
|
|
828
|
-
conn: sql.engine.Connection,
|
|
829
805
|
timestamp: float,
|
|
830
806
|
*,
|
|
831
807
|
rowids: Optional[Iterator[int]] = None,
|
|
@@ -837,20 +813,20 @@ class TableVersion:
|
|
|
837
813
|
self.version += 1
|
|
838
814
|
result = UpdateStatus()
|
|
839
815
|
num_rows, num_excs, cols_with_excs = self.store_tbl.insert_rows(
|
|
840
|
-
exec_plan,
|
|
816
|
+
exec_plan, v_min=self.version, rowids=rowids, abort_on_exc=abort_on_exc
|
|
841
817
|
)
|
|
842
818
|
result.num_rows = num_rows
|
|
843
819
|
result.num_excs = num_excs
|
|
844
820
|
result.num_computed_values += exec_plan.ctx.num_computed_exprs * num_rows
|
|
845
821
|
result.cols_with_excs = [f'{self.name}.{self.cols_by_id[cid].name}' for cid in cols_with_excs]
|
|
846
|
-
self._update_md(timestamp
|
|
822
|
+
self._update_md(timestamp)
|
|
847
823
|
|
|
848
824
|
# update views
|
|
849
825
|
for view in self.mutable_views:
|
|
850
826
|
from pixeltable.plan import Planner
|
|
851
827
|
|
|
852
|
-
plan, _ = Planner.create_view_load_plan(view.path, propagates_insert=True)
|
|
853
|
-
status = view._insert(plan,
|
|
828
|
+
plan, _ = Planner.create_view_load_plan(view.get().path, propagates_insert=True)
|
|
829
|
+
status = view.get()._insert(plan, timestamp, print_stats=print_stats)
|
|
854
830
|
result.num_rows += status.num_rows
|
|
855
831
|
result.num_excs += status.num_excs
|
|
856
832
|
result.num_computed_values += status.num_computed_values
|
|
@@ -886,22 +862,20 @@ class TableVersion:
|
|
|
886
862
|
if analysis_info.filter is not None:
|
|
887
863
|
raise excs.Error(f'Filter {analysis_info.filter} not expressible in SQL')
|
|
888
864
|
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
result.updated_cols = updated_cols
|
|
904
|
-
return result
|
|
865
|
+
plan, updated_cols, recomputed_cols = Planner.create_update_plan(self.path, update_spec, [], where, cascade)
|
|
866
|
+
from pixeltable.exprs import SqlElementCache
|
|
867
|
+
|
|
868
|
+
result = self.propagate_update(
|
|
869
|
+
plan,
|
|
870
|
+
where.sql_expr(SqlElementCache()) if where is not None else None,
|
|
871
|
+
recomputed_cols,
|
|
872
|
+
base_versions=[],
|
|
873
|
+
timestamp=time.time(),
|
|
874
|
+
cascade=cascade,
|
|
875
|
+
show_progress=True,
|
|
876
|
+
)
|
|
877
|
+
result.updated_cols = updated_cols
|
|
878
|
+
return result
|
|
905
879
|
|
|
906
880
|
def batch_update(
|
|
907
881
|
self,
|
|
@@ -920,33 +894,24 @@ class TableVersion:
|
|
|
920
894
|
assert len(rowids) == 0 or len(rowids) == len(batch)
|
|
921
895
|
cols_with_excs: set[str] = set()
|
|
922
896
|
|
|
923
|
-
|
|
924
|
-
from pixeltable.plan import Planner
|
|
897
|
+
from pixeltable.plan import Planner
|
|
925
898
|
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
if error_if_not_exists:
|
|
943
|
-
raise excs.Error(f'batch_update(): {len(unmatched_rows)} row(s) not found')
|
|
944
|
-
if insert_if_not_exists:
|
|
945
|
-
insert_status = self.insert(
|
|
946
|
-
unmatched_rows, None, conn=conn, print_stats=False, fail_on_exception=False
|
|
947
|
-
)
|
|
948
|
-
result += insert_status
|
|
949
|
-
return result
|
|
899
|
+
plan, row_update_node, delete_where_clause, updated_cols, recomputed_cols = Planner.create_batch_update_plan(
|
|
900
|
+
self.path, batch, rowids, cascade=cascade
|
|
901
|
+
)
|
|
902
|
+
result = self.propagate_update(
|
|
903
|
+
plan, delete_where_clause, recomputed_cols, base_versions=[], timestamp=time.time(), cascade=cascade
|
|
904
|
+
)
|
|
905
|
+
result.updated_cols = [c.qualified_name for c in updated_cols]
|
|
906
|
+
|
|
907
|
+
unmatched_rows = row_update_node.unmatched_rows()
|
|
908
|
+
if len(unmatched_rows) > 0:
|
|
909
|
+
if error_if_not_exists:
|
|
910
|
+
raise excs.Error(f'batch_update(): {len(unmatched_rows)} row(s) not found')
|
|
911
|
+
if insert_if_not_exists:
|
|
912
|
+
insert_status = self.insert(unmatched_rows, None, print_stats=False, fail_on_exception=False)
|
|
913
|
+
result += insert_status
|
|
914
|
+
return result
|
|
950
915
|
|
|
951
916
|
def _validate_update_spec(
|
|
952
917
|
self, value_spec: dict[str, Any], allow_pk: bool, allow_exprs: bool
|
|
@@ -1000,7 +965,6 @@ class TableVersion:
|
|
|
1000
965
|
where_clause: Optional[sql.ColumnElement],
|
|
1001
966
|
recomputed_view_cols: list[Column],
|
|
1002
967
|
base_versions: list[Optional[int]],
|
|
1003
|
-
conn: sql.engine.Connection,
|
|
1004
968
|
timestamp: float,
|
|
1005
969
|
cascade: bool,
|
|
1006
970
|
show_progress: bool = True,
|
|
@@ -1010,32 +974,26 @@ class TableVersion:
|
|
|
1010
974
|
# we're creating a new version
|
|
1011
975
|
self.version += 1
|
|
1012
976
|
result.num_rows, result.num_excs, cols_with_excs = self.store_tbl.insert_rows(
|
|
1013
|
-
plan,
|
|
977
|
+
plan, v_min=self.version, show_progress=show_progress
|
|
1014
978
|
)
|
|
1015
979
|
result.cols_with_excs = [f'{self.name}.{self.cols_by_id[cid].name}' for cid in cols_with_excs]
|
|
1016
980
|
self.store_tbl.delete_rows(
|
|
1017
|
-
self.version, base_versions=base_versions, match_on_vmin=True, where_clause=where_clause
|
|
981
|
+
self.version, base_versions=base_versions, match_on_vmin=True, where_clause=where_clause
|
|
1018
982
|
)
|
|
1019
|
-
self._update_md(timestamp
|
|
983
|
+
self._update_md(timestamp)
|
|
1020
984
|
|
|
1021
985
|
if cascade:
|
|
1022
986
|
base_versions = [None if plan is None else self.version] + base_versions # don't update in place
|
|
1023
987
|
# propagate to views
|
|
1024
988
|
for view in self.mutable_views:
|
|
1025
|
-
recomputed_cols = [col for col in recomputed_view_cols if col.tbl
|
|
989
|
+
recomputed_cols = [col for col in recomputed_view_cols if col.tbl == view]
|
|
1026
990
|
plan = None
|
|
1027
991
|
if len(recomputed_cols) > 0:
|
|
1028
992
|
from pixeltable.plan import Planner
|
|
1029
993
|
|
|
1030
|
-
plan = Planner.create_view_update_plan(view.path, recompute_targets=recomputed_cols)
|
|
1031
|
-
status = view.propagate_update(
|
|
1032
|
-
plan,
|
|
1033
|
-
None,
|
|
1034
|
-
recomputed_view_cols,
|
|
1035
|
-
base_versions=base_versions,
|
|
1036
|
-
conn=conn,
|
|
1037
|
-
timestamp=timestamp,
|
|
1038
|
-
cascade=True,
|
|
994
|
+
plan = Planner.create_view_update_plan(view.get().path, recompute_targets=recomputed_cols)
|
|
995
|
+
status = view.get().propagate_update(
|
|
996
|
+
plan, None, recomputed_view_cols, base_versions=base_versions, timestamp=timestamp, cascade=True
|
|
1039
997
|
)
|
|
1040
998
|
result.num_rows += status.num_rows
|
|
1041
999
|
result.num_excs += status.num_excs
|
|
@@ -1063,18 +1021,13 @@ class TableVersion:
|
|
|
1063
1021
|
raise excs.Error(f'Filter {analysis_info.filter} not expressible in SQL')
|
|
1064
1022
|
sql_where_clause = analysis_info.sql_where_clause
|
|
1065
1023
|
|
|
1066
|
-
|
|
1067
|
-
num_rows = self.propagate_delete(sql_where_clause, base_versions=[], conn=conn, timestamp=time.time())
|
|
1024
|
+
num_rows = self.propagate_delete(sql_where_clause, base_versions=[], timestamp=time.time())
|
|
1068
1025
|
|
|
1069
1026
|
status = UpdateStatus(num_rows=num_rows)
|
|
1070
1027
|
return status
|
|
1071
1028
|
|
|
1072
1029
|
def propagate_delete(
|
|
1073
|
-
self,
|
|
1074
|
-
where: Optional[exprs.Expr],
|
|
1075
|
-
base_versions: list[Optional[int]],
|
|
1076
|
-
conn: sql.engine.Connection,
|
|
1077
|
-
timestamp: float,
|
|
1030
|
+
self, where: Optional[exprs.Expr], base_versions: list[Optional[int]], timestamp: float
|
|
1078
1031
|
) -> int:
|
|
1079
1032
|
"""Delete rows in this table and propagate to views.
|
|
1080
1033
|
Args:
|
|
@@ -1084,17 +1037,17 @@ class TableVersion:
|
|
|
1084
1037
|
"""
|
|
1085
1038
|
sql_where_clause = where.sql_expr(exprs.SqlElementCache()) if where is not None else None
|
|
1086
1039
|
num_rows = self.store_tbl.delete_rows(
|
|
1087
|
-
self.version + 1, base_versions=base_versions, match_on_vmin=False, where_clause=sql_where_clause
|
|
1040
|
+
self.version + 1, base_versions=base_versions, match_on_vmin=False, where_clause=sql_where_clause
|
|
1088
1041
|
)
|
|
1089
1042
|
if num_rows > 0:
|
|
1090
1043
|
# we're creating a new version
|
|
1091
1044
|
self.version += 1
|
|
1092
|
-
self._update_md(timestamp
|
|
1045
|
+
self._update_md(timestamp)
|
|
1093
1046
|
else:
|
|
1094
1047
|
pass
|
|
1095
1048
|
for view in self.mutable_views:
|
|
1096
|
-
num_rows += view.propagate_delete(
|
|
1097
|
-
where=None, base_versions=[self.version] + base_versions,
|
|
1049
|
+
num_rows += view.get().propagate_delete(
|
|
1050
|
+
where=None, base_versions=[self.version] + base_versions, timestamp=timestamp
|
|
1098
1051
|
)
|
|
1099
1052
|
return num_rows
|
|
1100
1053
|
|
|
@@ -1103,22 +1056,20 @@ class TableVersion:
|
|
|
1103
1056
|
assert not self.is_snapshot
|
|
1104
1057
|
if self.version == 0:
|
|
1105
1058
|
raise excs.Error('Cannot revert version 0')
|
|
1106
|
-
|
|
1107
|
-
self._revert(session)
|
|
1108
|
-
session.commit()
|
|
1059
|
+
self._revert()
|
|
1109
1060
|
|
|
1110
|
-
def _delete_column(self, col: Column
|
|
1061
|
+
def _delete_column(self, col: Column) -> None:
|
|
1111
1062
|
"""Physically remove the column from the schema and the store table"""
|
|
1112
1063
|
if col.is_stored:
|
|
1113
|
-
self.store_tbl.drop_column(col
|
|
1064
|
+
self.store_tbl.drop_column(col)
|
|
1114
1065
|
self.cols.remove(col)
|
|
1115
1066
|
if col.name is not None:
|
|
1116
1067
|
del self.cols_by_name[col.name]
|
|
1117
1068
|
del self.cols_by_id[col.id]
|
|
1118
1069
|
|
|
1119
|
-
def _revert(self
|
|
1070
|
+
def _revert(self) -> None:
|
|
1120
1071
|
"""Reverts this table version and propagates to views"""
|
|
1121
|
-
conn =
|
|
1072
|
+
conn = Env.get().conn
|
|
1122
1073
|
# make sure we don't have a snapshot referencing this version
|
|
1123
1074
|
# (unclear how to express this with sqlalchemy)
|
|
1124
1075
|
query = (
|
|
@@ -1137,7 +1088,6 @@ class TableVersion:
|
|
|
1137
1088
|
)
|
|
1138
1089
|
)
|
|
1139
1090
|
|
|
1140
|
-
conn = session.connection()
|
|
1141
1091
|
# delete newly-added data
|
|
1142
1092
|
MediaStore.delete(self.id, version=self.version)
|
|
1143
1093
|
conn.execute(sql.delete(self.store_tbl.sa_tbl).where(self.store_tbl.sa_tbl.c.v_min == self.version))
|
|
@@ -1158,7 +1108,7 @@ class TableVersion:
|
|
|
1158
1108
|
if len(added_cols) > 0:
|
|
1159
1109
|
next_col_id = min(col.id for col in added_cols)
|
|
1160
1110
|
for col in added_cols:
|
|
1161
|
-
self._delete_column(col
|
|
1111
|
+
self._delete_column(col)
|
|
1162
1112
|
self.next_col_id = next_col_id
|
|
1163
1113
|
|
|
1164
1114
|
# remove newly-added indices from the lookup structures
|
|
@@ -1181,6 +1131,7 @@ class TableVersion:
|
|
|
1181
1131
|
for md in dropped_idx_md:
|
|
1182
1132
|
md.schema_version_drop = None
|
|
1183
1133
|
|
|
1134
|
+
session = Env.get().session
|
|
1184
1135
|
# we need to determine the preceding schema version and reload the schema
|
|
1185
1136
|
schema_version_md_dict = (
|
|
1186
1137
|
session.query(schema.TableSchemaVersion.md)
|
|
@@ -1224,7 +1175,7 @@ class TableVersion:
|
|
|
1224
1175
|
|
|
1225
1176
|
# propagate to views
|
|
1226
1177
|
for view in self.mutable_views:
|
|
1227
|
-
view._revert(
|
|
1178
|
+
view.get()._revert()
|
|
1228
1179
|
_logger.info(f'TableVersion {self.name}: reverted to version {self.version}')
|
|
1229
1180
|
|
|
1230
1181
|
def _init_external_stores(self, tbl_md: schema.TableMd) -> None:
|
|
@@ -1235,40 +1186,48 @@ class TableVersion:
|
|
|
1235
1186
|
self.external_stores[store.name] = store
|
|
1236
1187
|
|
|
1237
1188
|
def link_external_store(self, store: pxt.io.ExternalStore) -> None:
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
self._update_md(time.time(), conn, update_tbl_version=False)
|
|
1189
|
+
store.link(self) # May result in additional metadata changes
|
|
1190
|
+
self.external_stores[store.name] = store
|
|
1191
|
+
self._update_md(time.time(), update_tbl_version=False)
|
|
1242
1192
|
|
|
1243
1193
|
def unlink_external_store(self, store_name: str, delete_external_data: bool) -> None:
|
|
1244
1194
|
assert store_name in self.external_stores
|
|
1245
1195
|
store = self.external_stores[store_name]
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
self._update_md(time.time(), conn, update_tbl_version=False)
|
|
1196
|
+
store.unlink(self) # May result in additional metadata changes
|
|
1197
|
+
del self.external_stores[store_name]
|
|
1198
|
+
self._update_md(time.time(), update_tbl_version=False)
|
|
1250
1199
|
|
|
1251
1200
|
if delete_external_data and isinstance(store, pxt.io.external_store.Project):
|
|
1252
1201
|
store.delete()
|
|
1253
1202
|
|
|
1203
|
+
@property
|
|
1204
|
+
def is_snapshot(self) -> bool:
|
|
1205
|
+
return self.effective_version is not None
|
|
1206
|
+
|
|
1207
|
+
@property
|
|
1254
1208
|
def is_view(self) -> bool:
|
|
1255
|
-
return self.
|
|
1209
|
+
return self.view_md is not None
|
|
1210
|
+
|
|
1211
|
+
@property
|
|
1212
|
+
def include_base_columns(self) -> bool:
|
|
1213
|
+
return self.view_md is not None and self.view_md.include_base_columns
|
|
1256
1214
|
|
|
1215
|
+
@property
|
|
1257
1216
|
def is_component_view(self) -> bool:
|
|
1258
1217
|
return self.iterator_cls is not None
|
|
1259
1218
|
|
|
1260
1219
|
def is_insertable(self) -> bool:
|
|
1261
1220
|
"""Returns True if this corresponds to an InsertableTable"""
|
|
1262
|
-
return not self.is_snapshot and not self.is_view
|
|
1221
|
+
return not self.is_snapshot and not self.is_view
|
|
1263
1222
|
|
|
1264
1223
|
def is_iterator_column(self, col: Column) -> bool:
|
|
1265
1224
|
"""Returns True if col is produced by an iterator"""
|
|
1266
1225
|
# the iterator columns directly follow the pos column
|
|
1267
|
-
return self.is_component_view
|
|
1226
|
+
return self.is_component_view and col.id > 0 and col.id < self.num_iterator_cols + 1
|
|
1268
1227
|
|
|
1269
1228
|
def is_system_column(self, col: Column) -> bool:
|
|
1270
1229
|
"""Return True if column was created by Pixeltable"""
|
|
1271
|
-
if col.name == _POS_COLUMN_NAME and self.is_component_view
|
|
1230
|
+
if col.name == _POS_COLUMN_NAME and self.is_component_view:
|
|
1272
1231
|
return True
|
|
1273
1232
|
return False
|
|
1274
1233
|
|
|
@@ -1282,7 +1241,7 @@ class TableVersion:
|
|
|
1282
1241
|
|
|
1283
1242
|
def get_required_col_names(self) -> list[str]:
|
|
1284
1243
|
"""Return the names of all columns for which values must be specified in insert()"""
|
|
1285
|
-
assert not self.is_view
|
|
1244
|
+
assert not self.is_view
|
|
1286
1245
|
names = [c.name for c in self.cols_by_name.values() if not c.is_computed and not c.col_type.nullable]
|
|
1287
1246
|
return names
|
|
1288
1247
|
|
|
@@ -1318,8 +1277,8 @@ class TableVersion:
|
|
|
1318
1277
|
|
|
1319
1278
|
def num_rowid_columns(self) -> int:
|
|
1320
1279
|
"""Return the number of columns of the rowids, without accessing store_tbl"""
|
|
1321
|
-
if self.is_component_view
|
|
1322
|
-
return 1 + self.base.num_rowid_columns()
|
|
1280
|
+
if self.is_component_view:
|
|
1281
|
+
return 1 + self.base.get().num_rowid_columns()
|
|
1323
1282
|
return 1
|
|
1324
1283
|
|
|
1325
1284
|
@classmethod
|
|
@@ -1393,4 +1352,4 @@ class TableVersion:
|
|
|
1393
1352
|
|
|
1394
1353
|
id = UUID(d['id'])
|
|
1395
1354
|
effective_version = d['effective_version']
|
|
1396
|
-
return catalog.Catalog.get().
|
|
1355
|
+
return catalog.Catalog.get().get_tbl_version(id, effective_version)
|