-
Notifications
You must be signed in to change notification settings - Fork 3.6k
/
StatementRestrictions.java
871 lines (752 loc) · 35.6 KB
/
StatementRestrictions.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.cql3.restrictions;
import java.nio.ByteBuffer;
import java.util.*;
import com.google.common.base.Joiner;
import org.apache.cassandra.cql3.*;
import org.apache.cassandra.cql3.functions.Function;
import org.apache.cassandra.cql3.statements.Bound;
import org.apache.cassandra.cql3.statements.StatementType;
import org.apache.cassandra.db.*;
import org.apache.cassandra.db.filter.RowFilter;
import org.apache.cassandra.db.marshal.AbstractType;
import org.apache.cassandra.dht.*;
import org.apache.cassandra.exceptions.InvalidRequestException;
import org.apache.cassandra.index.Index;
import org.apache.cassandra.index.IndexRegistry;
import org.apache.cassandra.schema.ColumnMetadata;
import org.apache.cassandra.schema.TableMetadata;
import org.apache.cassandra.service.ClientState;
import org.apache.cassandra.utils.btree.BTreeSet;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkFalse;
import static org.apache.cassandra.cql3.statements.RequestValidations.checkNotNull;
import static org.apache.cassandra.cql3.statements.RequestValidations.invalidRequest;
/**
* The restrictions corresponding to the relations specified on the where-clause of CQL query.
*/
public final class StatementRestrictions
{
public static final String REQUIRES_ALLOW_FILTERING_MESSAGE =
"Cannot execute this query as it might involve data filtering and " +
"thus may have unpredictable performance. If you want to execute " +
"this query despite the performance unpredictability, use ALLOW FILTERING";
/**
* The type of statement
*/
private final StatementType type;
/**
* The Column Family meta data
*/
public final TableMetadata table;
/**
* Restrictions on partitioning columns
*/
private PartitionKeyRestrictions partitionKeyRestrictions;
/**
* Restrictions on clustering columns
*/
private ClusteringColumnRestrictions clusteringColumnsRestrictions;
/**
* Restriction on non-primary key columns (i.e. secondary index restrictions)
*/
private RestrictionSet nonPrimaryKeyRestrictions;
private Set<ColumnMetadata> notNullColumns;
/**
* The restrictions used to build the row filter
*/
private final IndexRestrictions filterRestrictions = new IndexRestrictions();
/**
* <code>true</code> if the secondary index need to be queried, <code>false</code> otherwise
*/
private boolean usesSecondaryIndexing;
/**
* Specify if the query will return a range of partition keys.
*/
private boolean isKeyRange;
/**
* <code>true</code> if nonPrimaryKeyRestrictions contains restriction on a regular column,
* <code>false</code> otherwise.
*/
private boolean hasRegularColumnsRestrictions;
/**
* Creates a new empty <code>StatementRestrictions</code>.
*
* @param type the type of statement
* @param table the column family meta data
* @return a new empty <code>StatementRestrictions</code>.
*/
public static StatementRestrictions empty(StatementType type, TableMetadata table)
{
return new StatementRestrictions(type, table, false);
}
private StatementRestrictions(StatementType type, TableMetadata table, boolean allowFiltering)
{
this.type = type;
this.table = table;
this.partitionKeyRestrictions = new PartitionKeySingleRestrictionSet(table.partitionKeyAsClusteringComparator());
this.clusteringColumnsRestrictions = new ClusteringColumnRestrictions(table, allowFiltering);
this.nonPrimaryKeyRestrictions = new RestrictionSet();
this.notNullColumns = new HashSet<>();
}
public StatementRestrictions(StatementType type,
TableMetadata table,
WhereClause whereClause,
VariableSpecifications boundNames,
boolean selectsOnlyStaticColumns,
boolean allowFiltering,
boolean forView)
{
this(type, table, whereClause, boundNames, selectsOnlyStaticColumns, type.allowUseOfSecondaryIndices(), allowFiltering, forView);
}
/*
* We want to override allowUseOfSecondaryIndices flag from the StatementType for MV statements
* to avoid initing the Keyspace and SecondaryIndexManager.
*/
public StatementRestrictions(StatementType type,
TableMetadata table,
WhereClause whereClause,
VariableSpecifications boundNames,
boolean selectsOnlyStaticColumns,
boolean allowUseOfSecondaryIndices,
boolean allowFiltering,
boolean forView)
{
this(type, table, allowFiltering);
IndexRegistry indexRegistry = null;
if (type.allowUseOfSecondaryIndices())
indexRegistry = IndexRegistry.obtain(table);
/*
* WHERE clause. For a given entity, rules are:
* - EQ relation conflicts with anything else (including a 2nd EQ)
* - Can't have more than one LT(E) relation (resp. GT(E) relation)
* - IN relation are restricted to row keys (for now) and conflicts with anything else (we could
* allow two IN for the same entity but that doesn't seem very useful)
* - The value_alias cannot be restricted in any way (we don't support wide rows with indexed value
* in CQL so far)
*/
for (Relation relation : whereClause.relations)
{
if (relation.operator() == Operator.IS_NOT)
{
if (!forView)
throw new InvalidRequestException("Unsupported restriction: " + relation);
this.notNullColumns.addAll(relation.toRestriction(table, boundNames).getColumnDefs());
}
else if (relation.isLIKE())
{
Restriction restriction = relation.toRestriction(table, boundNames);
if (!type.allowUseOfSecondaryIndices() || !restriction.hasSupportingIndex(indexRegistry))
throw new InvalidRequestException(String.format("LIKE restriction is only supported on properly " +
"indexed columns. %s is not valid.",
relation.toString()));
addRestriction(restriction);
}
else
{
addRestriction(relation.toRestriction(table, boundNames));
}
}
hasRegularColumnsRestrictions = nonPrimaryKeyRestrictions.hasRestrictionFor(ColumnMetadata.Kind.REGULAR);
boolean hasQueriableClusteringColumnIndex = false;
boolean hasQueriableIndex = false;
if (allowUseOfSecondaryIndices)
{
if (whereClause.containsCustomExpressions())
processCustomIndexExpressions(whereClause.expressions, boundNames, indexRegistry);
hasQueriableClusteringColumnIndex = clusteringColumnsRestrictions.hasSupportingIndex(indexRegistry);
hasQueriableIndex = !filterRestrictions.getCustomIndexExpressions().isEmpty()
|| hasQueriableClusteringColumnIndex
|| partitionKeyRestrictions.hasSupportingIndex(indexRegistry)
|| nonPrimaryKeyRestrictions.hasSupportingIndex(indexRegistry);
}
// At this point, the select statement if fully constructed, but we still have a few things to validate
processPartitionKeyRestrictions(hasQueriableIndex, allowFiltering, forView);
// Some but not all of the partition key columns have been specified;
// hence we need turn these restrictions into a row filter.
if (usesSecondaryIndexing || partitionKeyRestrictions.needFiltering(table))
filterRestrictions.add(partitionKeyRestrictions);
if (selectsOnlyStaticColumns && hasClusteringColumnsRestrictions())
{
// If the only updated/deleted columns are static, then we don't need clustering columns.
// And in fact, unless it is an INSERT, we reject if clustering colums are provided as that
// suggest something unintended. For instance, given:
// CREATE TABLE t (k int, v int, s int static, PRIMARY KEY (k, v))
// it can make sense to do:
// INSERT INTO t(k, v, s) VALUES (0, 1, 2)
// but both
// UPDATE t SET s = 3 WHERE k = 0 AND v = 1
// DELETE v FROM t WHERE k = 0 AND v = 1
// sounds like you don't really understand what your are doing.
if (type.isDelete() || type.isUpdate())
throw invalidRequest("Invalid restrictions on clustering columns since the %s statement modifies only static columns",
type);
if (type.isSelect())
throw invalidRequest("Cannot restrict clustering columns when selecting only static columns");
}
processClusteringColumnsRestrictions(hasQueriableIndex,
selectsOnlyStaticColumns,
forView,
allowFiltering);
// Covers indexes on the first clustering column (among others).
if (isKeyRange && hasQueriableClusteringColumnIndex)
usesSecondaryIndexing = true;
if (usesSecondaryIndexing || clusteringColumnsRestrictions.needFiltering())
filterRestrictions.add(clusteringColumnsRestrictions);
// Even if usesSecondaryIndexing is false at this point, we'll still have to use one if
// there is restrictions not covered by the PK.
if (!nonPrimaryKeyRestrictions.isEmpty())
{
if (!type.allowNonPrimaryKeyInWhereClause())
{
Collection<ColumnIdentifier> nonPrimaryKeyColumns =
ColumnMetadata.toIdentifiers(nonPrimaryKeyRestrictions.getColumnDefs());
throw invalidRequest("Non PRIMARY KEY columns found in where clause: %s ",
Joiner.on(", ").join(nonPrimaryKeyColumns));
}
if (hasQueriableIndex)
usesSecondaryIndexing = true;
else if (!allowFiltering)
throw invalidRequest(StatementRestrictions.REQUIRES_ALLOW_FILTERING_MESSAGE);
filterRestrictions.add(nonPrimaryKeyRestrictions);
}
if (usesSecondaryIndexing)
validateSecondaryIndexSelections();
}
private void addRestriction(Restriction restriction)
{
ColumnMetadata def = restriction.getFirstColumn();
if (def.isPartitionKey())
partitionKeyRestrictions = partitionKeyRestrictions.mergeWith(restriction);
else if (def.isClusteringColumn())
clusteringColumnsRestrictions = clusteringColumnsRestrictions.mergeWith(restriction);
else
nonPrimaryKeyRestrictions = nonPrimaryKeyRestrictions.addRestriction((SingleRestriction) restriction);
}
public void addFunctionsTo(List<Function> functions)
{
partitionKeyRestrictions.addFunctionsTo(functions);
clusteringColumnsRestrictions.addFunctionsTo(functions);
nonPrimaryKeyRestrictions.addFunctionsTo(functions);
}
// may be used by QueryHandler implementations
public IndexRestrictions getIndexRestrictions()
{
return filterRestrictions;
}
/**
* Returns the non-PK column that are restricted. If includeNotNullRestrictions is true, columns that are restricted
* by an IS NOT NULL restriction will be included, otherwise they will not be included (unless another restriction
* applies to them).
*/
public Set<ColumnMetadata> nonPKRestrictedColumns(boolean includeNotNullRestrictions)
{
Set<ColumnMetadata> columns = new HashSet<>();
for (Restrictions r : filterRestrictions.getRestrictions())
{
for (ColumnMetadata def : r.getColumnDefs())
if (!def.isPrimaryKeyColumn())
columns.add(def);
}
if (includeNotNullRestrictions)
{
for (ColumnMetadata def : notNullColumns)
{
if (!def.isPrimaryKeyColumn())
columns.add(def);
}
}
return columns;
}
/**
* @return the set of columns that have an IS NOT NULL restriction on them
*/
public Set<ColumnMetadata> notNullColumns()
{
return notNullColumns;
}
/**
* @return true if column is restricted by some restriction, false otherwise
*/
public boolean isRestricted(ColumnMetadata column)
{
if (notNullColumns.contains(column))
return true;
return getRestrictions(column.kind).getColumnDefs().contains(column);
}
/**
* Checks if the restrictions on the partition key has IN restrictions.
*
* @return <code>true</code> the restrictions on the partition key has an IN restriction, <code>false</code>
* otherwise.
*/
public boolean keyIsInRelation()
{
return partitionKeyRestrictions.hasIN();
}
/**
* Checks if the query request a range of partition keys.
*
* @return <code>true</code> if the query request a range of partition keys, <code>false</code> otherwise.
*/
public boolean isKeyRange()
{
return this.isKeyRange;
}
/**
* Checks if the specified column is restricted by an EQ restriction.
*
* @param columnDef the column definition
* @return <code>true</code> if the specified column is restricted by an EQ restiction, <code>false</code>
* otherwise.
*/
public boolean isColumnRestrictedByEq(ColumnMetadata columnDef)
{
Set<Restriction> restrictions = getRestrictions(columnDef.kind).getRestrictions(columnDef);
return restrictions.stream()
.filter(SingleRestriction.class::isInstance)
.anyMatch(p -> ((SingleRestriction) p).isEQ());
}
/**
* Returns the <code>Restrictions</code> for the specified type of columns.
*
* @param kind the column type
* @return the <code>Restrictions</code> for the specified type of columns
*/
private Restrictions getRestrictions(ColumnMetadata.Kind kind)
{
switch (kind)
{
case PARTITION_KEY: return partitionKeyRestrictions;
case CLUSTERING: return clusteringColumnsRestrictions;
default: return nonPrimaryKeyRestrictions;
}
}
/**
* Checks if the secondary index need to be queried.
*
* @return <code>true</code> if the secondary index need to be queried, <code>false</code> otherwise.
*/
public boolean usesSecondaryIndexing()
{
return this.usesSecondaryIndexing;
}
private void processPartitionKeyRestrictions(boolean hasQueriableIndex, boolean allowFiltering, boolean forView)
{
if (!type.allowPartitionKeyRanges())
{
checkFalse(partitionKeyRestrictions.isOnToken(),
"The token function cannot be used in WHERE clauses for %s statements", type);
if (partitionKeyRestrictions.hasUnrestrictedPartitionKeyComponents(table))
throw invalidRequest("Some partition key parts are missing: %s",
Joiner.on(", ").join(getPartitionKeyUnrestrictedComponents()));
// slice query
checkFalse(partitionKeyRestrictions.hasSlice(),
"Only EQ and IN relation are supported on the partition key (unless you use the token() function)"
+ " for %s statements", type);
}
else
{
// If there are no partition restrictions or there's only token restriction, we have to set a key range
if (partitionKeyRestrictions.isOnToken())
isKeyRange = true;
if (partitionKeyRestrictions.isEmpty() && partitionKeyRestrictions.hasUnrestrictedPartitionKeyComponents(table))
{
isKeyRange = true;
usesSecondaryIndexing = hasQueriableIndex;
}
// If there is a queriable index, no special condition is required on the other restrictions.
// But we still need to know 2 things:
// - If we don't have a queriable index, is the query ok
// - Is it queriable without 2ndary index, which is always more efficient
// If a component of the partition key is restricted by a relation, all preceding
// components must have a EQ. Only the last partition key component can be in IN relation.
if (partitionKeyRestrictions.needFiltering(table))
{
if (!allowFiltering && !forView && !hasQueriableIndex)
throw new InvalidRequestException(REQUIRES_ALLOW_FILTERING_MESSAGE);
isKeyRange = true;
usesSecondaryIndexing = hasQueriableIndex;
}
}
}
public boolean hasPartitionKeyRestrictions()
{
return !partitionKeyRestrictions.isEmpty();
}
/**
* Checks if the restrictions contain any non-primary key restrictions
* @return <code>true</code> if the restrictions contain any non-primary key restrictions, <code>false</code> otherwise.
*/
public boolean hasNonPrimaryKeyRestrictions()
{
return !nonPrimaryKeyRestrictions.isEmpty();
}
/**
* Returns the partition key components that are not restricted.
* @return the partition key components that are not restricted.
*/
private Collection<ColumnIdentifier> getPartitionKeyUnrestrictedComponents()
{
List<ColumnMetadata> list = new ArrayList<>(table.partitionKeyColumns());
list.removeAll(partitionKeyRestrictions.getColumnDefs());
return ColumnMetadata.toIdentifiers(list);
}
/**
* Checks if the restrictions on the partition key are token restrictions.
*
* @return <code>true</code> if the restrictions on the partition key are token restrictions,
* <code>false</code> otherwise.
*/
public boolean isPartitionKeyRestrictionsOnToken()
{
return partitionKeyRestrictions.isOnToken();
}
/**
* Checks if restrictions on the clustering key have IN restrictions.
*
* @return <code>true</code> if the restrictions on the clustering key have IN restrictions,
* <code>false</code> otherwise.
*/
public boolean clusteringKeyRestrictionsHasIN()
{
return clusteringColumnsRestrictions.hasIN();
}
/**
* Processes the clustering column restrictions.
*
* @param hasQueriableIndex <code>true</code> if some of the queried data are indexed, <code>false</code> otherwise
* @param selectsOnlyStaticColumns <code>true</code> if the selected or modified columns are all statics,
* <code>false</code> otherwise.
*/
private void processClusteringColumnsRestrictions(boolean hasQueriableIndex,
boolean selectsOnlyStaticColumns,
boolean forView,
boolean allowFiltering)
{
checkFalse(!type.allowClusteringColumnSlices() && clusteringColumnsRestrictions.hasSlice(),
"Slice restrictions are not supported on the clustering columns in %s statements", type);
if (!type.allowClusteringColumnSlices()
&& (!table.isCompactTable() || (table.isCompactTable() && !hasClusteringColumnsRestrictions())))
{
if (!selectsOnlyStaticColumns && hasUnrestrictedClusteringColumns())
throw invalidRequest("Some clustering keys are missing: %s",
Joiner.on(", ").join(getUnrestrictedClusteringColumns()));
}
else
{
checkFalse(clusteringColumnsRestrictions.hasContains() && !hasQueriableIndex && !allowFiltering,
"Clustering columns can only be restricted with CONTAINS with a secondary index or filtering");
if (hasClusteringColumnsRestrictions() && clusteringColumnsRestrictions.needFiltering())
{
if (hasQueriableIndex || forView)
{
usesSecondaryIndexing = true;
}
else if (!allowFiltering)
{
List<ColumnMetadata> clusteringColumns = table.clusteringColumns();
List<ColumnMetadata> restrictedColumns = new LinkedList<>(clusteringColumnsRestrictions.getColumnDefs());
for (int i = 0, m = restrictedColumns.size(); i < m; i++)
{
ColumnMetadata clusteringColumn = clusteringColumns.get(i);
ColumnMetadata restrictedColumn = restrictedColumns.get(i);
if (!clusteringColumn.equals(restrictedColumn))
{
throw invalidRequest("PRIMARY KEY column \"%s\" cannot be restricted as preceding column \"%s\" is not restricted",
restrictedColumn.name,
clusteringColumn.name);
}
}
}
}
}
}
/**
* Returns the clustering columns that are not restricted.
* @return the clustering columns that are not restricted.
*/
private Collection<ColumnIdentifier> getUnrestrictedClusteringColumns()
{
List<ColumnMetadata> missingClusteringColumns = new ArrayList<>(table.clusteringColumns());
missingClusteringColumns.removeAll(new LinkedList<>(clusteringColumnsRestrictions.getColumnDefs()));
return ColumnMetadata.toIdentifiers(missingClusteringColumns);
}
/**
* Checks if some clustering columns are not restricted.
* @return <code>true</code> if some clustering columns are not restricted, <code>false</code> otherwise.
*/
private boolean hasUnrestrictedClusteringColumns()
{
return table.clusteringColumns().size() != clusteringColumnsRestrictions.size();
}
private void processCustomIndexExpressions(List<CustomIndexExpression> expressions,
VariableSpecifications boundNames,
IndexRegistry indexRegistry)
{
if (expressions.size() > 1)
throw new InvalidRequestException(IndexRestrictions.MULTIPLE_EXPRESSIONS);
CustomIndexExpression expression = expressions.get(0);
QualifiedName name = expression.targetIndex;
if (name.hasKeyspace() && !name.getKeyspace().equals(table.keyspace))
throw IndexRestrictions.invalidIndex(expression.targetIndex, table);
if (!table.indexes.has(expression.targetIndex.getName()))
throw IndexRestrictions.indexNotFound(expression.targetIndex, table);
Index index = indexRegistry.getIndex(table.indexes.get(expression.targetIndex.getName()).get());
if (!index.getIndexMetadata().isCustom())
throw IndexRestrictions.nonCustomIndexInExpression(expression.targetIndex);
AbstractType<?> expressionType = index.customExpressionValueType();
if (expressionType == null)
throw IndexRestrictions.customExpressionNotSupported(expression.targetIndex);
expression.prepareValue(table, expressionType, boundNames);
filterRestrictions.add(expression);
}
public RowFilter getRowFilter(IndexRegistry indexRegistry, QueryOptions options)
{
if (filterRestrictions.isEmpty())
return RowFilter.NONE;
RowFilter filter = RowFilter.create();
for (Restrictions restrictions : filterRestrictions.getRestrictions())
restrictions.addRowFilterTo(filter, indexRegistry, options);
for (CustomIndexExpression expression : filterRestrictions.getCustomIndexExpressions())
expression.addToRowFilter(filter, table, options);
return filter;
}
/**
* Returns the partition keys for which the data is requested.
*
* @param options the query options
* @param state the client state
* @return the partition keys for which the data is requested.
*/
public List<ByteBuffer> getPartitionKeys(final QueryOptions options, ClientState state)
{
return partitionKeyRestrictions.values(options, state);
}
/**
* Returns the specified bound of the partition key.
*
* @param b the boundary type
* @param options the query options
* @return the specified bound of the partition key
*/
private ByteBuffer getPartitionKeyBound(Bound b, QueryOptions options)
{
// We deal with IN queries for keys in other places, so we know buildBound will return only one result
return partitionKeyRestrictions.bounds(b, options).get(0);
}
/**
* Returns the partition key bounds.
*
* @param options the query options
* @return the partition key bounds
*/
public AbstractBounds<PartitionPosition> getPartitionKeyBounds(QueryOptions options)
{
IPartitioner p = table.partitioner;
if (partitionKeyRestrictions.isOnToken())
{
return getPartitionKeyBoundsForTokenRestrictions(p, options);
}
return getPartitionKeyBounds(p, options);
}
private AbstractBounds<PartitionPosition> getPartitionKeyBounds(IPartitioner p,
QueryOptions options)
{
// Deal with unrestricted partition key components (special-casing is required to deal with 2i queries on the
// first component of a composite partition key) queries that filter on the partition key.
if (partitionKeyRestrictions.needFiltering(table))
return new Range<>(p.getMinimumToken().minKeyBound(), p.getMinimumToken().maxKeyBound());
ByteBuffer startKeyBytes = getPartitionKeyBound(Bound.START, options);
ByteBuffer finishKeyBytes = getPartitionKeyBound(Bound.END, options);
PartitionPosition startKey = PartitionPosition.ForKey.get(startKeyBytes, p);
PartitionPosition finishKey = PartitionPosition.ForKey.get(finishKeyBytes, p);
if (startKey.compareTo(finishKey) > 0 && !finishKey.isMinimum())
return null;
if (partitionKeyRestrictions.isInclusive(Bound.START))
{
return partitionKeyRestrictions.isInclusive(Bound.END)
? new Bounds<>(startKey, finishKey)
: new IncludingExcludingBounds<>(startKey, finishKey);
}
return partitionKeyRestrictions.isInclusive(Bound.END)
? new Range<>(startKey, finishKey)
: new ExcludingBounds<>(startKey, finishKey);
}
private AbstractBounds<PartitionPosition> getPartitionKeyBoundsForTokenRestrictions(IPartitioner p,
QueryOptions options)
{
Token startToken = getTokenBound(Bound.START, options, p);
Token endToken = getTokenBound(Bound.END, options, p);
boolean includeStart = partitionKeyRestrictions.isInclusive(Bound.START);
boolean includeEnd = partitionKeyRestrictions.isInclusive(Bound.END);
/*
* If we ask SP.getRangeSlice() for (token(200), token(200)], it will happily return the whole ring.
* However, wrapping range doesn't really make sense for CQL, and we want to return an empty result in that
* case (CASSANDRA-5573). So special case to create a range that is guaranteed to be empty.
*
* In practice, we want to return an empty result set if either startToken > endToken, or both are equal but
* one of the bound is excluded (since [a, a] can contains something, but not (a, a], [a, a) or (a, a)).
* Note though that in the case where startToken or endToken is the minimum token, then this special case
* rule should not apply.
*/
int cmp = startToken.compareTo(endToken);
if (!startToken.isMinimum() && !endToken.isMinimum()
&& (cmp > 0 || (cmp == 0 && (!includeStart || !includeEnd))))
return null;
PartitionPosition start = includeStart ? startToken.minKeyBound() : startToken.maxKeyBound();
PartitionPosition end = includeEnd ? endToken.maxKeyBound() : endToken.minKeyBound();
return new Range<>(start, end);
}
private Token getTokenBound(Bound b, QueryOptions options, IPartitioner p)
{
if (!partitionKeyRestrictions.hasBound(b))
return p.getMinimumToken();
ByteBuffer value = partitionKeyRestrictions.bounds(b, options).get(0);
checkNotNull(value, "Invalid null token value");
return p.getTokenFactory().fromByteArray(value);
}
/**
* Checks if the query has some restrictions on the clustering columns.
*
* @return <code>true</code> if the query has some restrictions on the clustering columns,
* <code>false</code> otherwise.
*/
public boolean hasClusteringColumnsRestrictions()
{
return !clusteringColumnsRestrictions.isEmpty();
}
/**
* Returns the requested clustering columns.
*
* @param options the query options
* @param state the client state
* @return the requested clustering columns
*/
public NavigableSet<Clustering<?>> getClusteringColumns(QueryOptions options, ClientState state)
{
// If this is a names command and the table is a static compact one, then as far as CQL is concerned we have
// only a single row which internally correspond to the static parts. In which case we want to return an empty
// set (since that's what ClusteringIndexNamesFilter expects).
if (table.isStaticCompactTable())
return BTreeSet.empty(table.comparator);
return clusteringColumnsRestrictions.valuesAsClustering(options, state);
}
/**
* Returns the bounds (start or end) of the clustering columns.
*
* @param b the bound type
* @param options the query options
* @return the bounds (start or end) of the clustering columns
*/
public NavigableSet<ClusteringBound<?>> getClusteringColumnsBounds(Bound b, QueryOptions options)
{
return clusteringColumnsRestrictions.boundsAsClustering(b, options);
}
/**
* Checks if the query returns a range of columns.
*
* @return <code>true</code> if the query returns a range of columns, <code>false</code> otherwise.
*/
public boolean isColumnRange()
{
int numberOfClusteringColumns = table.clusteringColumns().size();
if (table.isStaticCompactTable())
{
// For static compact tables we want to ignore the fake clustering column (note that if we weren't special casing,
// this would mean a 'SELECT *' on a static compact table would query whole partitions, even though we'll only return
// the static part as far as CQL is concerned. This is thus mostly an optimization to use the query-by-name path).
numberOfClusteringColumns = 0;
}
// it is a range query if it has at least one the column alias for which no relation is defined or is not EQ or IN.
return clusteringColumnsRestrictions.size() < numberOfClusteringColumns
|| !clusteringColumnsRestrictions.hasOnlyEqualityRestrictions();
}
/**
* Checks if the query need to use filtering.
* @return <code>true</code> if the query need to use filtering, <code>false</code> otherwise.
*/
public boolean needFiltering()
{
int numberOfRestrictions = filterRestrictions.getCustomIndexExpressions().size();
for (Restrictions restrictions : filterRestrictions.getRestrictions())
numberOfRestrictions += restrictions.size();
return numberOfRestrictions > 1
|| (numberOfRestrictions == 0 && !clusteringColumnsRestrictions.isEmpty())
|| (numberOfRestrictions != 0
&& nonPrimaryKeyRestrictions.hasMultipleContains());
}
private void validateSecondaryIndexSelections()
{
checkFalse(keyIsInRelation(),
"Select on indexed columns and with IN clause for the PRIMARY KEY are not supported");
}
/**
* Checks that all the primary key columns (partition key and clustering columns) are restricted by an equality
* relation ('=' or 'IN').
*
* @return <code>true</code> if all the primary key columns are restricted by an equality relation.
*/
public boolean hasAllPKColumnsRestrictedByEqualities()
{
return !isPartitionKeyRestrictionsOnToken()
&& !partitionKeyRestrictions.hasUnrestrictedPartitionKeyComponents(table)
&& (partitionKeyRestrictions.hasOnlyEqualityRestrictions())
&& !hasUnrestrictedClusteringColumns()
&& (clusteringColumnsRestrictions.hasOnlyEqualityRestrictions());
}
/**
* Checks if one of the restrictions applies to a regular column.
* @return {@code true} if one of the restrictions applies to a regular column, {@code false} otherwise.
*/
public boolean hasRegularColumnsRestrictions()
{
return hasRegularColumnsRestrictions;
}
/**
* Checks if the query is a full partitions selection.
* @return {@code true} if the query is a full partitions selection, {@code false} otherwise.
*/
private boolean queriesFullPartitions()
{
return !hasClusteringColumnsRestrictions() && !hasRegularColumnsRestrictions();
}
/**
* Determines if the query should return the static content when a partition without rows is returned (as a
* result set row with null for all other regular columns.)
*
* @return {@code true} if the query should return the static content when a partition without rows is returned,
* {@code false} otherwise.
*/
public boolean returnStaticContentOnPartitionWithNoRows()
{
if (table.isStaticCompactTable())
return true;
// The general rationale is that if some rows are specifically selected by the query (have clustering or
// regular columns restrictions), we ignore partitions that are empty outside of static content, but if it's
// a full partition query, then we include that content.
return queriesFullPartitions();
}
@Override
public String toString()
{
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}