• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

georgia-tech-db / eva / 91cae4db-ee39-47cb-81ee-210e6aabfa97

12 Oct 2023 08:31AM UTC coverage: 68.499% (+68.5%) from 0.0%
91cae4db-ee39-47cb-81ee-210e6aabfa97

Pull #1280

circle-ci

xzdandy
Minor
Pull Request #1280: Rebatch Optimization

50 of 50 new or added lines in 2 files covered. (100.0%)

8637 of 12609 relevant lines covered (68.5%)

0.68 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.96
/evadb/optimizer/operators.py
1
# coding=utf-8
2
# Copyright 2018-2023 EvaDB
3
#
4
# Licensed under the Apache License, Version 2.0 (the "License");
5
# you may not use this file except in compliance with the License.
6
# You may obtain a copy of the License at
7
#
8
#     http://www.apache.org/licenses/LICENSE-2.0
9
#
10
# Unless required by applicable law or agreed to in writing, software
11
# distributed under the License is distributed on an "AS IS" BASIS,
12
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
# See the License for the specific language governing permissions and
14
# limitations under the License.
15
from collections import deque
1✔
16
from enum import IntEnum, auto
1✔
17
from pathlib import Path
1✔
18
from typing import Any, List, Optional
1✔
19

20
from evadb.catalog.catalog_type import VectorStoreType
1✔
21
from evadb.catalog.models.column_catalog import ColumnCatalogEntry
1✔
22
from evadb.catalog.models.function_io_catalog import FunctionIOCatalogEntry
1✔
23
from evadb.catalog.models.function_metadata_catalog import FunctionMetadataCatalogEntry
1✔
24
from evadb.catalog.models.table_catalog import TableCatalogEntry
1✔
25
from evadb.catalog.models.utils import IndexCatalogEntry
1✔
26
from evadb.expression.abstract_expression import AbstractExpression
1✔
27
from evadb.expression.constant_value_expression import ConstantValueExpression
1✔
28
from evadb.expression.function_expression import FunctionExpression
1✔
29
from evadb.parser.alias import Alias
1✔
30
from evadb.parser.create_statement import ColumnDefinition
1✔
31
from evadb.parser.table_ref import TableInfo, TableRef
1✔
32
from evadb.parser.types import JoinType, ObjectType, ShowType
1✔
33

34

35
class OperatorType(IntEnum):
1✔
36
    """
37
    Manages enums for all the operators supported
38
    """
39

40
    DUMMY = auto()
1✔
41
    LOGICALEXCHANGE = auto()
1✔
42
    LOGICALGET = auto()
1✔
43
    LOGICALFILTER = auto()
1✔
44
    LOGICALPROJECT = auto()
1✔
45
    LOGICALINSERT = auto()
1✔
46
    LOGICALDELETE = auto()
1✔
47
    LOGICALCREATE = auto()
1✔
48
    LOGICALRENAME = auto()
1✔
49
    LOGICAL_DROP_OBJECT = auto()
1✔
50
    LOGICALCREATEFUNCTION = auto()
1✔
51
    LOGICALLOADDATA = auto()
1✔
52
    LOGICALQUERYDERIVEDGET = auto()
1✔
53
    LOGICALUNION = auto()
1✔
54
    LOGICALGROUPBY = auto()
1✔
55
    LOGICALORDERBY = auto()
1✔
56
    LOGICALLIMIT = auto()
1✔
57
    LOGICALSAMPLE = auto()
1✔
58
    LOGICALJOIN = auto()
1✔
59
    LOGICALFUNCTIONSCAN = auto()
1✔
60
    LOGICAL_SHOW = auto()
1✔
61
    LOGICALEXPLAIN = auto()
1✔
62
    LOGICALCREATEINDEX = auto()
1✔
63
    LOGICAL_APPLY_AND_MERGE = auto()
1✔
64
    LOGICAL_EXTRACT_OBJECT = auto()
1✔
65
    LOGICAL_VECTOR_INDEX_SCAN = auto()
1✔
66
    LOGICAL_USE = auto()
1✔
67
    LOGICAL_REBATACH = auto()
1✔
68
    LOGICALDELIMITER = auto()
1✔
69

70

71
class Operator:
1✔
72
    """Base class for logical plan of operators
73
    Arguments:
74
        op_type: {OperatorType} -- {the opr type held by this node}
75
        children: {List} -- {the list of operator children for this node}
76
    """
77

78
    def __init__(self, op_type: OperatorType, children=None):
1✔
79
        self._opr_type = op_type
1✔
80
        self._children = children or []
1✔
81

82
    @property
1✔
83
    def children(self):
1✔
84
        return self._children
1✔
85

86
    @children.setter
1✔
87
    def children(self, children):
1✔
88
        self._children = children
1✔
89

90
    @property
1✔
91
    def opr_type(self):
1✔
92
        return self._opr_type
1✔
93

94
    def append_child(self, child: "Operator"):
1✔
95
        self.children.append(child)
1✔
96

97
    def clear_children(self):
1✔
98
        self.children = []
1✔
99

100
    def __str__(self) -> str:
1✔
101
        return "%s[%s](%s)" % (
1✔
102
            type(self).__name__,
103
            hex(id(self)),
104
            ", ".join("%s=%s" % item for item in vars(self).items()),
105
        )
106

107
    def __eq__(self, other):
1✔
108
        is_subtree_equal = True
×
109
        if not isinstance(other, Operator):
×
110
            return False
×
111
        if len(self.children) != len(other.children):
×
112
            return False
×
113
        for child1, child2 in zip(self.children, other.children):
×
114
            is_subtree_equal = is_subtree_equal and (child1 == child2)
×
115
        return is_subtree_equal
×
116

117
    def is_logical(self):
1✔
118
        return self._opr_type < OperatorType.LOGICALDELIMITER
1✔
119

120
    def __hash__(self) -> int:
1✔
121
        return hash((self.opr_type, tuple(self.children)))
1✔
122

123
    def __copy__(self):
1✔
124
        # deepcopy the children
125
        cls = self.__class__
1✔
126
        result = cls.__new__(cls)
1✔
127
        for k, v in self.__dict__.items():
1✔
128
            if k == "_children":
1✔
129
                setattr(result, k, [])
1✔
130
            else:
131
                setattr(result, k, v)
1✔
132
        return result
1✔
133

134
    def bfs(self):
1✔
135
        """Returns a generator which visits all nodes in operator tree in
136
        breadth-first search (BFS) traversal order.
137

138
        Returns:
139
            the generator object.
140
        """
141
        queue = deque([self])
1✔
142
        while queue:
1✔
143
            node = queue.popleft()
1✔
144
            yield node
1✔
145
            for child in node.children:
1✔
146
                queue.append(child)
1✔
147

148
    def find_all(self, operator_type: Any):
1✔
149
        """Returns a generator which visits all the nodes in operator tree and yields one that matches the passed `operator_type`.
150

151
        Args:
152
            operator_type (Any): operator type to match with
153

154
        Returns:
155
            the generator object.
156
        """
157

158
        for node in self.bfs():
1✔
159
            if isinstance(node, operator_type) or self.opr_type == operator_type:
1✔
160
                yield node
1✔
161

162

163
class Dummy(Operator):
1✔
164
    """
165
    Acts as a placeholder for matching any operator in optimizer.
166
    It tracks the group_id of the matching operator.
167
    """
168

169
    def __init__(self, group_id: int, opr: Operator):
1✔
170
        super().__init__(OperatorType.DUMMY, None)
1✔
171
        self.group_id = group_id
1✔
172
        self.opr = opr
1✔
173

174

175
class LogicalGet(Operator):
1✔
176
    def __init__(
1✔
177
        self,
178
        video: TableRef,
179
        table_obj: TableCatalogEntry,
180
        alias: str,
181
        predicate: AbstractExpression = None,
182
        target_list: List[AbstractExpression] = None,
183
        sampling_rate: int = None,
184
        sampling_type: str = None,
185
        chunk_params: dict = {},
186
        children=None,
187
    ):
188
        self._video = video
1✔
189
        self._table_obj = table_obj
1✔
190
        self._alias = alias
1✔
191
        self._predicate = predicate
1✔
192
        self._target_list = target_list
1✔
193
        self._sampling_rate = sampling_rate
1✔
194
        self._sampling_type = sampling_type
1✔
195
        self.chunk_params = chunk_params
1✔
196
        super().__init__(OperatorType.LOGICALGET, children)
1✔
197

198
    @property
1✔
199
    def video(self):
1✔
200
        return self._video
1✔
201

202
    @property
1✔
203
    def table_obj(self):
1✔
204
        return self._table_obj
1✔
205

206
    @property
1✔
207
    def alias(self):
1✔
208
        return self._alias
1✔
209

210
    @property
1✔
211
    def predicate(self):
1✔
212
        return self._predicate
1✔
213

214
    @property
1✔
215
    def target_list(self):
1✔
216
        return self._target_list
1✔
217

218
    @property
1✔
219
    def sampling_rate(self):
1✔
220
        return self._sampling_rate
1✔
221

222
    @property
1✔
223
    def sampling_type(self):
1✔
224
        return self._sampling_type
1✔
225

226
    def __eq__(self, other):
1✔
227
        is_subtree_equal = super().__eq__(other)
×
228
        if not isinstance(other, LogicalGet):
×
229
            return False
×
230
        return (
×
231
            is_subtree_equal
232
            and self.video == other.video
233
            and self.table_obj == other.table_obj
234
            and self.alias == other.alias
235
            and self.predicate == other.predicate
236
            and self.target_list == other.target_list
237
            and self.sampling_rate == other.sampling_rate
238
            and self.sampling_type == other.sampling_type
239
            and self.chunk_params == other.chunk_params
240
        )
241

242
    def __hash__(self) -> int:
1✔
243
        return hash(
1✔
244
            (
245
                super().__hash__(),
246
                self.alias,
247
                self.video,
248
                self.table_obj,
249
                self.predicate,
250
                tuple(self.target_list or []),
251
                self.sampling_rate,
252
                self.sampling_type,
253
                frozenset(self.chunk_params.items()),
254
            )
255
        )
256

257

258
class LogicalQueryDerivedGet(Operator):
1✔
259
    def __init__(
1✔
260
        self,
261
        alias: str,
262
        predicate: AbstractExpression = None,
263
        target_list: List[AbstractExpression] = None,
264
        children: List = None,
265
    ):
266
        super().__init__(OperatorType.LOGICALQUERYDERIVEDGET, children=children)
1✔
267
        self._alias = alias
1✔
268
        self.predicate = predicate
1✔
269
        self.target_list = target_list or []
1✔
270

271
    @property
1✔
272
    def alias(self):
1✔
273
        return self._alias
1✔
274

275
    def __eq__(self, other):
1✔
276
        is_subtree_equal = super().__eq__(other)
×
277
        if not isinstance(other, LogicalQueryDerivedGet):
×
278
            return False
×
279
        return (
×
280
            is_subtree_equal
281
            and self.predicate == other.predicate
282
            and self.target_list == other.target_list
283
            and self.alias == other.alias
284
        )
285

286
    def __hash__(self) -> int:
1✔
287
        return hash(
1✔
288
            (
289
                super().__hash__(),
290
                self.alias,
291
                self.predicate,
292
                tuple(self.target_list),
293
            )
294
        )
295

296

297
class LogicalFilter(Operator):
1✔
298
    def __init__(self, predicate: AbstractExpression, children=None):
1✔
299
        self._predicate = predicate
1✔
300
        super().__init__(OperatorType.LOGICALFILTER, children)
1✔
301

302
    @property
1✔
303
    def predicate(self):
1✔
304
        return self._predicate
1✔
305

306
    def __eq__(self, other):
1✔
307
        is_subtree_equal = super().__eq__(other)
×
308
        if not isinstance(other, LogicalFilter):
×
309
            return False
×
310
        return is_subtree_equal and self.predicate == other.predicate
×
311

312
    def __hash__(self) -> int:
1✔
313
        return hash((super().__hash__(), self.predicate))
1✔
314

315

316
class LogicalProject(Operator):
1✔
317
    def __init__(self, target_list: List[AbstractExpression], children=None):
1✔
318
        super().__init__(OperatorType.LOGICALPROJECT, children)
1✔
319
        self._target_list = target_list
1✔
320

321
    @property
1✔
322
    def target_list(self):
1✔
323
        return self._target_list
1✔
324

325
    def __eq__(self, other):
1✔
326
        is_subtree_equal = super().__eq__(other)
×
327
        if not isinstance(other, LogicalProject):
×
328
            return False
×
329
        return is_subtree_equal and self.target_list == other.target_list
×
330

331
    def __hash__(self) -> int:
1✔
332
        return hash((super().__hash__(), tuple(self.target_list)))
1✔
333

334

335
class LogicalGroupBy(Operator):
1✔
336
    def __init__(self, groupby_clause: ConstantValueExpression, children: List = None):
1✔
337
        super().__init__(OperatorType.LOGICALGROUPBY, children)
1✔
338
        self._groupby_clause = groupby_clause
1✔
339

340
    @property
1✔
341
    def groupby_clause(self):
1✔
342
        return self._groupby_clause
1✔
343

344
    def __eq__(self, other):
1✔
345
        is_subtree_equal = super().__eq__(other)
×
346
        if not isinstance(other, LogicalGroupBy):
×
347
            return False
×
348
        return is_subtree_equal and self.groupby_clause == other.groupby_clause
×
349

350
    def __hash__(self) -> int:
1✔
351
        return hash((super().__hash__(), self.groupby_clause))
1✔
352

353

354
class LogicalOrderBy(Operator):
1✔
355
    def __init__(self, orderby_list: List, children: List = None):
1✔
356
        super().__init__(OperatorType.LOGICALORDERBY, children)
1✔
357
        self._orderby_list = orderby_list
1✔
358

359
    @property
1✔
360
    def orderby_list(self):
1✔
361
        return self._orderby_list
1✔
362

363
    def __eq__(self, other):
1✔
364
        is_subtree_equal = super().__eq__(other)
×
365
        if not isinstance(other, LogicalOrderBy):
×
366
            return False
×
367
        return is_subtree_equal and self.orderby_list == other.orderby_list
×
368

369
    def __hash__(self) -> int:
1✔
370
        return hash((super().__hash__(), tuple(self.orderby_list)))
1✔
371

372

373
class LogicalLimit(Operator):
1✔
374
    def __init__(self, limit_count: ConstantValueExpression, children: List = None):
1✔
375
        super().__init__(OperatorType.LOGICALLIMIT, children)
1✔
376
        self._limit_count = limit_count
1✔
377

378
    @property
1✔
379
    def limit_count(self):
1✔
380
        return self._limit_count
1✔
381

382
    def __eq__(self, other):
1✔
383
        is_subtree_equal = super().__eq__(other)
×
384
        if not isinstance(other, LogicalLimit):
×
385
            return False
×
386
        return is_subtree_equal and self.limit_count == other.limit_count
×
387

388
    def __hash__(self) -> int:
1✔
389
        return hash((super().__hash__(), self.limit_count))
1✔
390

391

392
class LogicalSample(Operator):
1✔
393
    def __init__(
1✔
394
        self,
395
        sample_freq: ConstantValueExpression,
396
        sample_type: ConstantValueExpression,
397
        children: List = None,
398
    ):
399
        super().__init__(OperatorType.LOGICALSAMPLE, children)
1✔
400
        self._sample_freq = sample_freq
1✔
401
        self._sample_type = sample_type
1✔
402

403
    @property
1✔
404
    def sample_freq(self):
1✔
405
        return self._sample_freq
1✔
406

407
    @property
1✔
408
    def sample_type(self):
1✔
409
        return self._sample_type
1✔
410

411
    def __eq__(self, other):
1✔
412
        is_subtree_equal = super().__eq__(other)
×
413
        if not isinstance(other, LogicalSample):
×
414
            return False
×
415
        return (
×
416
            is_subtree_equal
417
            and self.sample_freq == other.sample_freq
418
            and self.sample_type == other.sample_type
419
        )
420

421
    def __hash__(self) -> int:
1✔
422
        return hash((super().__hash__(), self.sample_freq, self.sample_type))
1✔
423

424

425
class LogicalUnion(Operator):
1✔
426
    def __init__(self, all: bool, children: List = None):
1✔
427
        super().__init__(OperatorType.LOGICALUNION, children)
×
428
        self._all = all
×
429

430
    @property
1✔
431
    def all(self):
1✔
432
        return self._all
×
433

434
    def __eq__(self, other):
1✔
435
        is_subtree_equal = super().__eq__(other)
×
436
        if not isinstance(other, LogicalUnion):
×
437
            return False
×
438
        return is_subtree_equal and self.all == other.all
×
439

440
    def __hash__(self) -> int:
1✔
441
        return hash((super().__hash__(), self.all))
×
442

443

444
class LogicalInsert(Operator):
1✔
445
    """[Logical Node for Insert operation]
446

447
    Arguments:
448
        table(TableCatalogEntry): table to insert data into
449
        column_list{List[AbstractExpression]}:
450
            [After binding annotated column_list]
451
        value_list{List[AbstractExpression]}:
452
            [value list to insert]
453
    """
454

455
    def __init__(
1✔
456
        self,
457
        table: TableCatalogEntry,
458
        column_list: List[AbstractExpression],
459
        value_list: List[AbstractExpression],
460
        children: List = None,
461
    ):
462
        super().__init__(OperatorType.LOGICALINSERT, children)
1✔
463
        self._table = table
1✔
464
        self._column_list = column_list
1✔
465
        self._value_list = value_list
1✔
466

467
    @property
1✔
468
    def table(self):
1✔
469
        return self._table
1✔
470

471
    @property
1✔
472
    def value_list(self):
1✔
473
        return self._value_list
1✔
474

475
    @property
1✔
476
    def column_list(self):
1✔
477
        return self._column_list
1✔
478

479
    def __eq__(self, other):
1✔
480
        is_subtree_equal = super().__eq__(other)
×
481
        if not isinstance(other, LogicalInsert):
×
482
            return False
×
483
        return (
×
484
            is_subtree_equal
485
            and self.table == other.table
486
            and self.value_list == other.value_list
487
            and self.column_list == other.column_list
488
        )
489

490
    def __hash__(self) -> int:
1✔
491
        return hash(
1✔
492
            (
493
                super().__hash__(),
494
                self.table,
495
                tuple(self.value_list),
496
                tuple(self.column_list),
497
            )
498
        )
499

500

501
class LogicalDelete(Operator):
1✔
502
    """[Logical Node for Delete Operation]
503

504
    Arguments:
505
        table_ref(TableCatalogEntry): table to delete tuples from,
506
        where_clause(AbstractExpression): the predicate used to select which rows to delete,
507

508
    """
509

510
    def __init__(
1✔
511
        self,
512
        table_ref: TableRef,
513
        where_clause: AbstractExpression = None,
514
        children=None,
515
    ):
516
        super().__init__(OperatorType.LOGICALDELETE, children)
×
517
        self._table_ref = table_ref
×
518
        self._where_clause = where_clause
×
519

520
    @property
1✔
521
    def table_ref(self):
1✔
522
        return self._table_ref
×
523

524
    @property
1✔
525
    def where_clause(self):
1✔
526
        return self._where_clause
×
527

528
    def __eq__(self, other):
1✔
529
        is_subtree_equal = super().__eq__(other)
×
530
        if not isinstance(other, LogicalDelete):
×
531
            return False
×
532
        return (
×
533
            is_subtree_equal
534
            and self.table_ref == other.table_ref
535
            and self.where_clause == other.where_clause
536
        )
537

538
    def __hash__(self) -> int:
1✔
539
        return hash(
×
540
            (
541
                super().__hash__(),
542
                self.table_ref,
543
                self.where_clause,
544
            )
545
        )
546

547

548
class LogicalCreate(Operator):
1✔
549
    """Logical node for create table operations
550

551
    Arguments:
552
        video {TableRef}: [video table that is to be created]
553
        column_list {List[ColumnDefinition]}:
554
        if_not_exists {bool}: [create table if exists]
555

556
    """
557

558
    def __init__(
1✔
559
        self,
560
        video: TableInfo,
561
        column_list: List[ColumnDefinition],
562
        if_not_exists: bool = False,
563
        children: List = None,
564
    ):
565
        super().__init__(OperatorType.LOGICALCREATE, children)
1✔
566
        self._video = video
1✔
567
        self._column_list = column_list
1✔
568
        self._if_not_exists = if_not_exists
1✔
569

570
    @property
1✔
571
    def video(self):
1✔
572
        return self._video
1✔
573

574
    @property
1✔
575
    def column_list(self):
1✔
576
        return self._column_list
1✔
577

578
    @property
1✔
579
    def if_not_exists(self):
1✔
580
        return self._if_not_exists
1✔
581

582
    def __eq__(self, other):
1✔
583
        is_subtree_equal = super().__eq__(other)
×
584
        if not isinstance(other, LogicalCreate):
×
585
            return False
×
586
        return (
×
587
            is_subtree_equal
588
            and self.video == other.video
589
            and self.column_list == other.column_list
590
            and self.if_not_exists == other.if_not_exists
591
        )
592

593
    def __hash__(self) -> int:
1✔
594
        return hash(
1✔
595
            (
596
                super().__hash__(),
597
                self.video,
598
                tuple(self.column_list),
599
                self.if_not_exists,
600
            )
601
        )
602

603

604
class LogicalRename(Operator):
1✔
605
    """Logical node for rename table operations
606

607
    Arguments:
608
        old_table {TableRef}: [old table that is to be renamed]
609
        new_name {TableInfo}: [new name for the old table]
610
    """
611

612
    def __init__(self, old_table_ref: TableRef, new_name: TableInfo, children=None):
1✔
613
        super().__init__(OperatorType.LOGICALRENAME, children)
1✔
614
        self._new_name = new_name
1✔
615
        self._old_table_ref = old_table_ref
1✔
616

617
    @property
1✔
618
    def new_name(self):
1✔
619
        return self._new_name
1✔
620

621
    @property
1✔
622
    def old_table_ref(self):
1✔
623
        return self._old_table_ref
1✔
624

625
    def __eq__(self, other):
1✔
626
        is_subtree_equal = super().__eq__(other)
×
627
        if not isinstance(other, LogicalRename):
×
628
            return False
×
629
        return (
×
630
            is_subtree_equal
631
            and self._new_name == other._new_name
632
            and self._old_table_ref == other._old_table_ref
633
        )
634

635
    def __hash__(self) -> int:
1✔
636
        return hash((super().__hash__(), self._new_name, self._old_table_ref))
1✔
637

638

639
class LogicalCreateFunction(Operator):
1✔
640
    """
641
    Logical node for create function operations
642

643
    Attributes:
644
        name: str
645
            function_name provided by the user required
646
        or_replace: bool
647
            if true should overwrite if function with same name exists
648
        if_not_exists: bool
649
            if true should skip if function with same name exists
650
        inputs: List[FunctionIOCatalogEntry]
651
            function inputs, annotated list similar to table columns
652
        outputs: List[FunctionIOCatalogEntry]
653
            function outputs, annotated list similar to table columns
654
        impl_path: Path
655
            file path which holds the implementation of the function.
656
            This file should be placed in the function directory and
657
            the path provided should be relative to the function dir.
658
        function_type: str
659
            function type. it ca be object detection, classification etc.
660
    """
661

662
    def __init__(
1✔
663
        self,
664
        name: str,
665
        or_replace: bool,
666
        if_not_exists: bool,
667
        inputs: List[FunctionIOCatalogEntry],
668
        outputs: List[FunctionIOCatalogEntry],
669
        impl_path: Path,
670
        function_type: str = None,
671
        metadata: List[FunctionMetadataCatalogEntry] = None,
672
        children: List = None,
673
    ):
674
        super().__init__(OperatorType.LOGICALCREATEFUNCTION, children)
1✔
675
        self._name = name
1✔
676
        self._or_replace = or_replace
1✔
677
        self._if_not_exists = if_not_exists
1✔
678
        self._inputs = inputs
1✔
679
        self._outputs = outputs
1✔
680
        self._impl_path = impl_path
1✔
681
        self._function_type = function_type
1✔
682
        self._metadata = metadata
1✔
683

684
    @property
1✔
685
    def name(self):
1✔
686
        return self._name
1✔
687

688
    @property
1✔
689
    def or_replace(self):
1✔
690
        return self._or_replace
1✔
691

692
    @property
1✔
693
    def if_not_exists(self):
1✔
694
        return self._if_not_exists
1✔
695

696
    @property
1✔
697
    def inputs(self):
1✔
698
        return self._inputs
1✔
699

700
    @property
1✔
701
    def outputs(self):
1✔
702
        return self._outputs
1✔
703

704
    @property
1✔
705
    def impl_path(self):
1✔
706
        return self._impl_path
1✔
707

708
    @property
1✔
709
    def function_type(self):
1✔
710
        return self._function_type
1✔
711

712
    @property
1✔
713
    def metadata(self):
1✔
714
        return self._metadata
1✔
715

716
    def __eq__(self, other):
1✔
717
        is_subtree_equal = super().__eq__(other)
×
718
        if not isinstance(other, LogicalCreateFunction):
×
719
            return False
×
720
        return (
×
721
            is_subtree_equal
722
            and self.name == other.name
723
            and self.or_replace == other.or_replace
724
            and self.if_not_exists == other.if_not_exists
725
            and self.inputs == other.inputs
726
            and self.outputs == other.outputs
727
            and self.function_type == other.function_type
728
            and self.impl_path == other.impl_path
729
            and self.metadata == other.metadata
730
        )
731

732
    def __hash__(self) -> int:
1✔
733
        return hash(
1✔
734
            (
735
                super().__hash__(),
736
                self.name,
737
                self.or_replace,
738
                self.if_not_exists,
739
                tuple(self.inputs),
740
                tuple(self.outputs),
741
                self.function_type,
742
                self.impl_path,
743
                tuple(self.metadata),
744
            )
745
        )
746

747

748
class LogicalDropObject(Operator):
1✔
749
    """
750
    Logical node for DROP Object operations
751

752
    Attributes:
753
        object_type: ObjectType
754
        name: str
755
            Function name provided by the user
756
        if_exists: bool
757
            if false, throws an error when no function with name exists
758
            else logs a warning
759
    """
760

761
    def __init__(
1✔
762
        self, object_type: ObjectType, name: str, if_exists: bool, children: List = None
763
    ):
764
        super().__init__(OperatorType.LOGICAL_DROP_OBJECT, children)
1✔
765
        self._object_type = object_type
1✔
766
        self._name = name
1✔
767
        self._if_exists = if_exists
1✔
768

769
    @property
1✔
770
    def object_type(self):
1✔
771
        return self._object_type
1✔
772

773
    @property
1✔
774
    def name(self):
1✔
775
        return self._name
1✔
776

777
    @property
1✔
778
    def if_exists(self):
1✔
779
        return self._if_exists
1✔
780

781
    def __eq__(self, other):
1✔
782
        is_subtree_equal = super().__eq__(other)
×
783
        if not isinstance(other, LogicalDropObject):
×
784
            return False
×
785
        return (
×
786
            is_subtree_equal
787
            and self.object_type == other.object_type
788
            and self.name == other.name
789
            and self.if_exists == other.if_exists
790
        )
791

792
    def __hash__(self) -> int:
1✔
793
        return hash((super().__hash__(), self.object_type, self.name, self.if_exists))
1✔
794

795

796
class LogicalLoadData(Operator):
1✔
797
    """Logical node for load data operation
798

799
    Arguments:
800
        table(TableCatalogEntry): table to load data into
801
        path(Path): file path from where we are loading data
802
    """
803

804
    def __init__(
1✔
805
        self,
806
        table_info: TableInfo,
807
        path: Path,
808
        column_list: List[AbstractExpression] = None,
809
        file_options: dict = dict(),
810
        children: List = None,
811
    ):
812
        super().__init__(OperatorType.LOGICALLOADDATA, children=children)
1✔
813
        self._table_info = table_info
1✔
814
        self._path = path
1✔
815
        self._column_list = column_list or []
1✔
816
        self._file_options = file_options
1✔
817

818
    @property
1✔
819
    def table_info(self):
1✔
820
        return self._table_info
1✔
821

822
    @property
1✔
823
    def path(self):
1✔
824
        return self._path
1✔
825

826
    @property
1✔
827
    def column_list(self):
1✔
828
        return self._column_list
1✔
829

830
    @property
1✔
831
    def file_options(self):
1✔
832
        return self._file_options
1✔
833

834
    def __str__(self):
1✔
835
        return "LogicalLoadData(table: {}, path: {}, \
×
836
                column_list: {}, \
837
                file_options: {})".format(
838
            self.table_info, self.path, self.column_list, self.file_options
839
        )
840

841
    def __eq__(self, other):
1✔
842
        is_subtree_equal = super().__eq__(other)
×
843
        if not isinstance(other, LogicalLoadData):
×
844
            return False
×
845
        return (
×
846
            is_subtree_equal
847
            and self.table_info == other.table_info
848
            and self.path == other.path
849
            and self.column_list == other.column_list
850
            and self.file_options == other.file_options
851
        )
852

853
    def __hash__(self) -> int:
1✔
854
        return hash(
1✔
855
            (
856
                super().__hash__(),
857
                self.table_info,
858
                self.path,
859
                tuple(self.column_list),
860
                frozenset(self.file_options.items()),
861
            )
862
        )
863

864

865
class LogicalFunctionScan(Operator):
1✔
866
    """
867
    Logical node for function table scans
868

869
    Attributes:
870
        func_expr: AbstractExpression
871
            function_expression that yield a table like output
872
    """
873

874
    def __init__(
1✔
875
        self,
876
        func_expr: AbstractExpression,
877
        alias: Alias,
878
        do_unnest: bool = False,
879
        children: List = None,
880
    ):
881
        super().__init__(OperatorType.LOGICALFUNCTIONSCAN, children)
1✔
882
        self._func_expr = func_expr
1✔
883
        self._do_unnest = do_unnest
1✔
884
        self._alias = alias
1✔
885

886
    @property
1✔
887
    def alias(self):
1✔
888
        return self._alias
1✔
889

890
    @property
1✔
891
    def func_expr(self):
1✔
892
        return self._func_expr
1✔
893

894
    @property
1✔
895
    def do_unnest(self):
1✔
896
        return self._do_unnest
1✔
897

898
    def __eq__(self, other):
1✔
899
        is_subtree_equal = super().__eq__(other)
×
900
        if not isinstance(other, LogicalFunctionScan):
×
901
            return False
×
902
        return (
×
903
            is_subtree_equal
904
            and self.func_expr == other.func_expr
905
            and self.do_unnest == other.do_unnest
906
            and self.alias == other.alias
907
        )
908

909
    def __hash__(self) -> int:
1✔
910
        return hash((super().__hash__(), self.func_expr, self.do_unnest, self.alias))
1✔
911

912

913
class LogicalExtractObject(Operator):
1✔
914
    def __init__(
1✔
915
        self,
916
        detector: FunctionExpression,
917
        tracker: FunctionExpression,
918
        alias: Alias,
919
        do_unnest: bool = False,
920
        children: List = None,
921
    ):
922
        super().__init__(OperatorType.LOGICAL_EXTRACT_OBJECT, children)
×
923
        self.detector = detector
×
924
        self.tracker = tracker
×
925
        self.do_unnest = do_unnest
×
926
        self.alias = alias
×
927

928
    def __eq__(self, other):
1✔
929
        is_subtree_equal = super().__eq__(other)
×
930
        if not isinstance(other, LogicalExtractObject):
×
931
            return False
×
932
        return (
×
933
            is_subtree_equal
934
            and self.detector == other.detector
935
            and self.tracker == other.tracker
936
            and self.do_unnest == other.do_unnest
937
            and self.alias == other.alias
938
        )
939

940
    def __hash__(self) -> int:
1✔
941
        return hash(
×
942
            (
943
                super().__hash__(),
944
                self.detector,
945
                self.tracker,
946
                self.do_unnest,
947
                self.alias,
948
            )
949
        )
950

951

952
class LogicalJoin(Operator):
1✔
953
    """
954
    Logical node for join operators
955

956
    Attributes:
957
        join_type: JoinType
958
            Join type provided by the user - Lateral, Inner, Outer
959
        join_predicate: AbstractExpression
960
            condition/predicate expression used to join the tables
961
    """
962

963
    def __init__(
1✔
964
        self,
965
        join_type: JoinType,
966
        join_predicate: AbstractExpression = None,
967
        left_keys: List[ColumnCatalogEntry] = None,
968
        right_keys: List[ColumnCatalogEntry] = None,
969
        children: List = None,
970
    ):
971
        super().__init__(OperatorType.LOGICALJOIN, children)
1✔
972
        self._join_type = join_type
1✔
973
        self._join_predicate = join_predicate
1✔
974
        self._left_keys = left_keys
1✔
975
        self._right_keys = right_keys
1✔
976
        self._join_project = None
1✔
977

978
    @property
1✔
979
    def join_type(self):
1✔
980
        return self._join_type
1✔
981

982
    @property
1✔
983
    def join_predicate(self):
1✔
984
        return self._join_predicate
1✔
985

986
    @property
1✔
987
    def left_keys(self):
1✔
988
        return self._left_keys
1✔
989

990
    @property
1✔
991
    def right_keys(self):
1✔
992
        return self._right_keys
1✔
993

994
    @property
1✔
995
    def join_project(self):
1✔
996
        return self._join_project
1✔
997

998
    def lhs(self):
1✔
999
        return self.children[0]
1✔
1000

1001
    def rhs(self):
1✔
1002
        return self.children[1]
1✔
1003

1004
    def __eq__(self, other):
1✔
1005
        is_subtree_equal = super().__eq__(other)
×
1006
        if not isinstance(other, LogicalJoin):
×
1007
            return False
×
1008
        return (
×
1009
            is_subtree_equal
1010
            and self.join_type == other.join_type
1011
            and self.join_predicate == other.join_predicate
1012
            and self.left_keys == other.left_keys
1013
            and self.right_keys == other.right_keys
1014
            and self.join_project == other.join_project
1015
        )
1016

1017
    def __hash__(self) -> int:
1✔
1018
        return hash(
1✔
1019
            (
1020
                super().__hash__(),
1021
                self.join_type,
1022
                self.join_predicate,
1023
                self.left_keys,
1024
                self.right_keys,
1025
                tuple(self.join_project or []),
1026
            )
1027
        )
1028

1029

1030
class LogicalShow(Operator):
1✔
1031
    def __init__(
1✔
1032
        self, show_type: ShowType, show_val: Optional[str] = "", children: List = None
1033
    ):
1034
        super().__init__(OperatorType.LOGICAL_SHOW, children)
1✔
1035
        self._show_type = show_type
1✔
1036
        self._show_val = show_val
1✔
1037

1038
    @property
1✔
1039
    def show_type(self):
1✔
1040
        return self._show_type
1✔
1041

1042
    @property
1✔
1043
    def show_val(self):
1✔
1044
        return self._show_val
1✔
1045

1046
    def __eq__(self, other):
1✔
1047
        is_subtree_equal = super().__eq__(other)
×
1048
        if not isinstance(other, LogicalShow):
×
1049
            return False
×
1050
        return (
×
1051
            is_subtree_equal
1052
            and self.show_type == other.show_type
1053
            and self.show_val == other.show_val
1054
        )
1055

1056
    def __hash__(self) -> int:
1✔
1057
        return hash((super().__hash__(), self.show_type, self.show_val))
1✔
1058

1059

1060
class LogicalExchange(Operator):
1✔
1061
    def __init__(self, children=None):
1✔
1062
        super().__init__(OperatorType.LOGICALEXCHANGE, children)
×
1063

1064
    def __eq__(self, other):
1✔
1065
        is_subtree_equal = super().__eq__(other)
×
1066
        if not isinstance(other, LogicalExchange):
×
1067
            return False
×
1068
        return is_subtree_equal
×
1069

1070

1071
class LogicalExplain(Operator):
1✔
1072
    def __init__(self, children: List = None):
1✔
1073
        super().__init__(OperatorType.LOGICALEXPLAIN, children)
×
1074
        assert len(children) == 1, "EXPLAIN command only takes one child"
×
1075
        self._explainable_opr = children[0]
×
1076

1077
    @property
1✔
1078
    def explainable_opr(self):
1✔
1079
        return self._explainable_opr
×
1080

1081
    def __eq__(self, other):
1✔
1082
        is_subtree_equal = super().__eq__(other)
×
1083
        if not isinstance(other, LogicalExplain):
×
1084
            return False
×
1085
        return is_subtree_equal and self._explainable_opr == other.explainable_opr
×
1086

1087
    def __hash__(self) -> int:
1✔
1088
        return hash((super().__hash__(), self._explainable_opr))
×
1089

1090

1091
class LogicalCreateIndex(Operator):
1✔
1092
    def __init__(
1✔
1093
        self,
1094
        name: str,
1095
        if_not_exists: bool,
1096
        table_ref: TableRef,
1097
        col_list: List[ColumnDefinition],
1098
        vector_store_type: VectorStoreType,
1099
        project_expr_list: List[AbstractExpression],
1100
        index_def: str,
1101
        children: List = None,
1102
    ):
1103
        super().__init__(OperatorType.LOGICALCREATEINDEX, children)
1✔
1104
        self._name = name
1✔
1105
        self._if_not_exists = if_not_exists
1✔
1106
        self._table_ref = table_ref
1✔
1107
        self._col_list = col_list
1✔
1108
        self._vector_store_type = vector_store_type
1✔
1109
        self._project_expr_list = project_expr_list
1✔
1110
        self._index_def = index_def
1✔
1111

1112
    @property
1✔
1113
    def name(self):
1✔
1114
        return self._name
1✔
1115

1116
    @property
1✔
1117
    def if_not_exists(self):
1✔
1118
        return self._if_not_exists
1✔
1119

1120
    @property
1✔
1121
    def table_ref(self):
1✔
1122
        return self._table_ref
1✔
1123

1124
    @property
1✔
1125
    def col_list(self):
1✔
1126
        return self._col_list
1✔
1127

1128
    @property
1✔
1129
    def vector_store_type(self):
1✔
1130
        return self._vector_store_type
1✔
1131

1132
    @property
1✔
1133
    def project_expr_list(self):
1✔
1134
        return self._project_expr_list
1✔
1135

1136
    @property
1✔
1137
    def index_def(self):
1✔
1138
        return self._index_def
1✔
1139

1140
    def __eq__(self, other):
1✔
1141
        is_subtree_equal = super().__eq__(other)
×
1142
        if not isinstance(other, LogicalCreateIndex):
×
1143
            return False
×
1144
        return (
×
1145
            is_subtree_equal
1146
            and self.name == other.name
1147
            and self.if_not_exists == other.if_not_exists
1148
            and self.table_ref == other.table_ref
1149
            and self.col_list == other.col_list
1150
            and self.vector_store_type == other.vector_store_type
1151
            and self.project_expr_list == other.project_expr_list
1152
            and self.index_def == other.index_def
1153
        )
1154

1155
    def __hash__(self) -> int:
1✔
1156
        return hash(
1✔
1157
            (
1158
                super().__hash__(),
1159
                self.name,
1160
                self.if_not_exists,
1161
                self.table_ref,
1162
                tuple(self.col_list),
1163
                self.vector_store_type,
1164
                tuple(self.project_expr_list),
1165
                self.index_def,
1166
            )
1167
        )
1168

1169

1170
class LogicalApplyAndMerge(Operator):
1✔
1171
    """Evaluate the function expression on the input data and return the merged output.
1172
    This operator simplifies the process of evaluating functions on a table source.
1173
    Currently, it performs an inner join while merging the function output with the
1174
    input data. This means that if the function does not return any output for a given
1175
    input row, that row will be dropped from the output. We can consider expanding this
1176
    to support left joins and other types of joins in the future.
1177
    """
1178

1179
    def __init__(
1✔
1180
        self,
1181
        func_expr: FunctionExpression,
1182
        alias: Alias,
1183
        do_unnest: bool = False,
1184
        children: List = None,
1185
    ):
1186
        super().__init__(OperatorType.LOGICAL_APPLY_AND_MERGE, children)
1✔
1187
        self._func_expr = func_expr
1✔
1188
        self._do_unnest = do_unnest
1✔
1189
        self._alias = alias
1✔
1190
        self._merge_type = JoinType.INNER_JOIN
1✔
1191

1192
    @property
1✔
1193
    def alias(self):
1✔
1194
        return self._alias
1✔
1195

1196
    @property
1✔
1197
    def func_expr(self):
1✔
1198
        return self._func_expr
1✔
1199

1200
    @property
1✔
1201
    def do_unnest(self):
1✔
1202
        return self._do_unnest
1✔
1203

1204
    def __eq__(self, other):
1✔
1205
        is_subtree_equal = super().__eq__(other)
×
1206
        if not isinstance(other, LogicalApplyAndMerge):
×
1207
            return False
×
1208
        return (
×
1209
            is_subtree_equal
1210
            and self.func_expr == other.func_expr
1211
            and self.do_unnest == other.do_unnest
1212
            and self.alias == other.alias
1213
            and self._merge_type == other._merge_type
1214
        )
1215

1216
    def __hash__(self) -> int:
1✔
1217
        return hash(
1✔
1218
            (
1219
                super().__hash__(),
1220
                self.func_expr,
1221
                self.do_unnest,
1222
                self.alias,
1223
                self._merge_type,
1224
            )
1225
        )
1226

1227

1228
class LogicalRebatch(Operator):
1✔
1229
    def __init__(
1✔
1230
        self, batch_mem_size: int = 1, batch_size: int = 1, children: List = None
1231
    ):
1232
        super().__init__(OperatorType.LOGICAL_REBATACH, children)
×
1233
        self.batch_mem_size = batch_mem_size
×
1234
        self.batch_size = batch_size
×
1235

1236
    def __eq__(self, other):
1✔
1237
        is_subtree_equal = super().__eq__(other)
×
1238
        if not isinstance(other, LogicalRebatch):
×
1239
            return False
×
1240
        return (
×
1241
            is_subtree_equal
1242
            and self.batch_mem_size == other.batch_mem_size
1243
            and self.batch_size == other.batch_size
1244
        )
1245

1246
    def __hash__(self) -> int:
1✔
1247
        return hash(
×
1248
            (
1249
                super().__hash__(),
1250
                self.batch_mem_size,
1251
                self.batch_size,
1252
            )
1253
        )
1254

1255

1256
class LogicalVectorIndexScan(Operator):
1✔
1257
    def __init__(
1✔
1258
        self,
1259
        index: IndexCatalogEntry,
1260
        limit_count: ConstantValueExpression,
1261
        search_query_expr: FunctionExpression,
1262
        children: List = None,
1263
    ):
1264
        super().__init__(OperatorType.LOGICAL_VECTOR_INDEX_SCAN, children)
×
1265
        self._index = index
×
1266
        self._limit_count = limit_count
×
1267
        self._search_query_expr = search_query_expr
×
1268

1269
    @property
1✔
1270
    def index(self):
1✔
1271
        return self._index
×
1272

1273
    @property
1✔
1274
    def limit_count(self):
1✔
1275
        return self._limit_count
×
1276

1277
    @property
1✔
1278
    def search_query_expr(self):
1✔
1279
        return self._search_query_expr
×
1280

1281
    def __eq__(self, other):
1✔
1282
        is_subtree_equal = super().__eq__(other)
×
1283
        if not isinstance(other, LogicalVectorIndexScan):
×
1284
            return False
×
1285
        return (
×
1286
            is_subtree_equal
1287
            and self.index == other.index
1288
            and self.limit_count == other.limit_count
1289
            and self.search_query_expr == other.search_query_expr
1290
        )
1291

1292
    def __hash__(self) -> int:
1✔
1293
        return hash(
×
1294
            (
1295
                super().__hash__(),
1296
                self.index,
1297
                self.limit_count,
1298
                self.search_query_expr,
1299
            )
1300
        )
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc