17
17
from collections import defaultdict
18
18
from functools import total_ordering
19
19
from hashlib import md5
20
- from itertools import islice , cycle
21
20
import json
22
21
import logging
23
22
import re
50
49
cql_keywords = set ((
51
50
'add' , 'aggregate' , 'all' , 'allow' , 'alter' , 'and' , 'apply' , 'as' , 'asc' , 'ascii' , 'authorize' , 'batch' , 'begin' ,
52
51
'bigint' , 'blob' , 'boolean' , 'by' , 'called' , 'clustering' , 'columnfamily' , 'compact' , 'contains' , 'count' ,
53
- 'counter' , 'create' , 'custom' , 'date' , 'decimal' , 'delete' , 'desc' , 'describe' , 'distinct' , 'double' , 'drop' ,
52
+ 'counter' , 'create' , 'custom' , 'date' , 'decimal' , 'delete' , 'desc' , 'describe' , 'deterministic' , ' distinct' , 'double' , 'drop' ,
54
53
'entries' , 'execute' , 'exists' , 'filtering' , 'finalfunc' , 'float' , 'from' , 'frozen' , 'full' , 'function' ,
55
54
'functions' , 'grant' , 'if' , 'in' , 'index' , 'inet' , 'infinity' , 'initcond' , 'input' , 'insert' , 'int' , 'into' , 'is' , 'json' ,
56
- 'key' , 'keys' , 'keyspace' , 'keyspaces' , 'language' , 'limit' , 'list' , 'login' , 'map' , 'materialized' , 'modify' , 'nan' , 'nologin' ,
55
+ 'key' , 'keys' , 'keyspace' , 'keyspaces' , 'language' , 'limit' , 'list' , 'login' , 'map' , 'materialized' , 'modify' , 'monotonic' , ' nan' , 'nologin' ,
57
56
'norecursive' , 'nosuperuser' , 'not' , 'null' , 'of' , 'on' , 'options' , 'or' , 'order' , 'password' , 'permission' ,
58
57
'permissions' , 'primary' , 'rename' , 'replace' , 'returns' , 'revoke' , 'role' , 'roles' , 'schema' , 'select' , 'set' ,
59
58
'sfunc' , 'smallint' , 'static' , 'storage' , 'stype' , 'superuser' , 'table' , 'text' , 'time' , 'timestamp' , 'timeuuid' ,
68
67
69
68
cql_keywords_unreserved = set ((
70
69
'aggregate' , 'all' , 'as' , 'ascii' , 'bigint' , 'blob' , 'boolean' , 'called' , 'clustering' , 'compact' , 'contains' ,
71
- 'count' , 'counter' , 'custom' , 'date' , 'decimal' , 'distinct' , 'double' , 'exists' , 'filtering' , 'finalfunc' , 'float' ,
70
+ 'count' , 'counter' , 'custom' , 'date' , 'decimal' , 'deterministic' , ' distinct' , 'double' , 'exists' , 'filtering' , 'finalfunc' , 'float' ,
72
71
'frozen' , 'function' , 'functions' , 'inet' , 'initcond' , 'input' , 'int' , 'json' , 'key' , 'keys' , 'keyspaces' ,
73
- 'language' , 'list' , 'login' , 'map' , 'nologin' , 'nosuperuser' , 'options' , 'password' , 'permission' , 'permissions' ,
72
+ 'language' , 'list' , 'login' , 'map' , 'monotonic' , ' nologin' , 'nosuperuser' , 'options' , 'password' , 'permission' , 'permissions' ,
74
73
'returns' , 'role' , 'roles' , 'sfunc' , 'smallint' , 'static' , 'storage' , 'stype' , 'superuser' , 'text' , 'time' ,
75
74
'timestamp' , 'timeuuid' , 'tinyint' , 'trigger' , 'ttl' , 'tuple' , 'type' , 'user' , 'users' , 'uuid' , 'values' , 'varchar' ,
76
75
'varint' , 'writetime'
@@ -125,7 +124,8 @@ def export_schema_as_string(self):
125
124
def refresh (self , connection , timeout , target_type = None , change_type = None , ** kwargs ):
126
125
127
126
server_version = self .get_host (connection .endpoint ).release_version
128
- parser = get_schema_parser (connection , server_version , timeout )
127
+ dse_version = self .get_host (connection .endpoint ).dse_version
128
+ parser = get_schema_parser (connection , server_version , dse_version , timeout )
129
129
130
130
if not target_type :
131
131
self ._rebuild_all (parser )
@@ -661,7 +661,7 @@ class KeyspaceMetadata(object):
661
661
virtual = False
662
662
"""
663
663
A boolean indicating if this is a virtual keyspace or not. Always ``False``
664
- for clusters running pre-4.0 versions of Cassandra .
664
+ for clusters running Cassandra pre-4.0 and DSE pre-6.7 versions .
665
665
666
666
.. versionadded:: 3.15
667
667
"""
@@ -893,8 +893,15 @@ class Aggregate(object):
893
893
Type of the aggregate state
894
894
"""
895
895
896
+ deterministic = None
897
+ """
898
+ Flag indicating if this function is guaranteed to produce the same result
899
+ for a particular input and state. This is available only with DSE >=6.0.
900
+ """
901
+
896
902
def __init__ (self , keyspace , name , argument_types , state_func ,
897
- state_type , final_func , initial_condition , return_type ):
903
+ state_type , final_func , initial_condition , return_type ,
904
+ deterministic ):
898
905
self .keyspace = keyspace
899
906
self .name = name
900
907
self .argument_types = argument_types
@@ -903,6 +910,7 @@ def __init__(self, keyspace, name, argument_types, state_func,
903
910
self .final_func = final_func
904
911
self .initial_condition = initial_condition
905
912
self .return_type = return_type
913
+ self .deterministic = deterministic
906
914
907
915
def as_cql_query (self , formatted = False ):
908
916
"""
@@ -923,6 +931,7 @@ def as_cql_query(self, formatted=False):
923
931
924
932
ret += '' .join ((sep , 'FINALFUNC ' , protect_name (self .final_func ))) if self .final_func else ''
925
933
ret += '' .join ((sep , 'INITCOND ' , self .initial_condition )) if self .initial_condition is not None else ''
934
+ ret += '{}DETERMINISTIC' .format (sep ) if self .deterministic else ''
926
935
927
936
return ret
928
937
@@ -984,8 +993,27 @@ class Function(object):
984
993
(convenience function to avoid handling nulls explicitly if the result will just be null)
985
994
"""
986
995
996
+ deterministic = None
997
+ """
998
+ Flag indicating if this function is guaranteed to produce the same result
999
+ for a particular input. This is available only for DSE >=6.0.
1000
+ """
1001
+
1002
+ monotonic = None
1003
+ """
1004
+ Flag indicating if this function is guaranteed to increase or decrease
1005
+ monotonically on any of its arguments. This is available only for DSE >=6.0.
1006
+ """
1007
+
1008
+ monotonic_on = None
1009
+ """
1010
+ A list containing the argument or arguments over which this function is
1011
+ monotonic. This is available only for DSE >=6.0.
1012
+ """
1013
+
987
1014
def __init__ (self , keyspace , name , argument_types , argument_names ,
988
- return_type , language , body , called_on_null_input ):
1015
+ return_type , language , body , called_on_null_input ,
1016
+ deterministic , monotonic , monotonic_on ):
989
1017
self .keyspace = keyspace
990
1018
self .name = name
991
1019
self .argument_types = argument_types
@@ -996,6 +1024,9 @@ def __init__(self, keyspace, name, argument_types, argument_names,
996
1024
self .language = language
997
1025
self .body = body
998
1026
self .called_on_null_input = called_on_null_input
1027
+ self .deterministic = deterministic
1028
+ self .monotonic = monotonic
1029
+ self .monotonic_on = monotonic_on
999
1030
1000
1031
def as_cql_query (self , formatted = False ):
1001
1032
"""
@@ -1012,10 +1043,25 @@ def as_cql_query(self, formatted=False):
1012
1043
lang = self .language
1013
1044
body = self .body
1014
1045
on_null = "CALLED" if self .called_on_null_input else "RETURNS NULL"
1046
+ deterministic_token = ('DETERMINISTIC{}' .format (sep )
1047
+ if self .deterministic else
1048
+ '' )
1049
+ monotonic_tokens = '' # default for nonmonotonic function
1050
+ if self .monotonic :
1051
+ # monotonic on all arguments; ignore self.monotonic_on
1052
+ monotonic_tokens = 'MONOTONIC{}' .format (sep )
1053
+ elif self .monotonic_on :
1054
+ # if monotonic == False and monotonic_on is nonempty, we know that
1055
+ # monotonicity was specified with MONOTONIC ON <arg>, so there's
1056
+ # exactly 1 value there
1057
+ monotonic_tokens = 'MONOTONIC ON {}{}' .format (self .monotonic_on [0 ],
1058
+ sep )
1015
1059
1016
1060
return "CREATE FUNCTION %(keyspace)s.%(name)s(%(arg_list)s)%(sep)s" \
1017
1061
"%(on_null)s ON NULL INPUT%(sep)s" \
1018
1062
"RETURNS %(typ)s%(sep)s" \
1063
+ "%(deterministic_token)s" \
1064
+ "%(monotonic_tokens)s" \
1019
1065
"LANGUAGE %(lang)s%(sep)s" \
1020
1066
"AS $$%(body)s$$" % locals ()
1021
1067
@@ -1102,7 +1148,7 @@ def primary_key(self):
1102
1148
virtual = False
1103
1149
"""
1104
1150
A boolean indicating if this is a virtual table or not. Always ``False``
1105
- for clusters running pre-4.0 versions of Cassandra .
1151
+ for clusters running Cassandra pre-4.0 and DSE pre-6.7 versions .
1106
1152
1107
1153
.. versionadded:: 3.15
1108
1154
"""
@@ -1733,6 +1779,9 @@ def _query_build_rows(self, query_string, build_func):
1733
1779
1734
1780
1735
1781
class SchemaParserV22 (_SchemaParser ):
1782
+ """
1783
+ For C* 2.2+
1784
+ """
1736
1785
_SELECT_KEYSPACES = "SELECT * FROM system.schema_keyspaces"
1737
1786
_SELECT_COLUMN_FAMILIES = "SELECT * FROM system.schema_columnfamilies"
1738
1787
_SELECT_COLUMNS = "SELECT * FROM system.schema_columns"
@@ -1884,10 +1933,14 @@ def _build_user_type(cls, usertype_row):
1884
1933
@classmethod
1885
1934
def _build_function (cls , function_row ):
1886
1935
return_type = cls ._schema_type_to_cql (function_row ['return_type' ])
1936
+ deterministic = function_row .get ('deterministic' , False )
1937
+ monotonic = function_row .get ('monotonic' , False )
1938
+ monotonic_on = function_row .get ('monotonic_on' , ())
1887
1939
return Function (function_row ['keyspace_name' ], function_row ['function_name' ],
1888
1940
function_row [cls ._function_agg_arument_type_col ], function_row ['argument_names' ],
1889
1941
return_type , function_row ['language' ], function_row ['body' ],
1890
- function_row ['called_on_null_input' ])
1942
+ function_row ['called_on_null_input' ],
1943
+ deterministic , monotonic , monotonic_on )
1891
1944
1892
1945
@classmethod
1893
1946
def _build_aggregate (cls , aggregate_row ):
@@ -1899,7 +1952,8 @@ def _build_aggregate(cls, aggregate_row):
1899
1952
return_type = cls ._schema_type_to_cql (aggregate_row ['return_type' ])
1900
1953
return Aggregate (aggregate_row ['keyspace_name' ], aggregate_row ['aggregate_name' ],
1901
1954
aggregate_row ['signature' ], aggregate_row ['state_func' ], state_type ,
1902
- aggregate_row ['final_func' ], initial_condition , return_type )
1955
+ aggregate_row ['final_func' ], initial_condition , return_type ,
1956
+ aggregate_row .get ('deterministic' , False ))
1903
1957
1904
1958
def _build_table_metadata (self , row , col_rows = None , trigger_rows = None ):
1905
1959
keyspace_name = row ["keyspace_name" ]
@@ -2230,6 +2284,9 @@ def _schema_type_to_cql(type_string):
2230
2284
2231
2285
2232
2286
class SchemaParserV3 (SchemaParserV22 ):
2287
+ """
2288
+ For C* 3.0+
2289
+ """
2233
2290
_SELECT_KEYSPACES = "SELECT * FROM system_schema.keyspaces"
2234
2291
_SELECT_TABLES = "SELECT * FROM system_schema.tables"
2235
2292
_SELECT_COLUMNS = "SELECT * FROM system_schema.columns"
@@ -2316,7 +2373,8 @@ def _build_keyspace_metadata_internal(row):
2316
2373
def _build_aggregate (aggregate_row ):
2317
2374
return Aggregate (aggregate_row ['keyspace_name' ], aggregate_row ['aggregate_name' ],
2318
2375
aggregate_row ['argument_types' ], aggregate_row ['state_func' ], aggregate_row ['state_type' ],
2319
- aggregate_row ['final_func' ], aggregate_row ['initcond' ], aggregate_row ['return_type' ])
2376
+ aggregate_row ['final_func' ], aggregate_row ['initcond' ], aggregate_row ['return_type' ],
2377
+ aggregate_row .get ('deterministic' , False ))
2320
2378
2321
2379
def _build_table_metadata (self , row , col_rows = None , trigger_rows = None , index_rows = None , virtual = False ):
2322
2380
keyspace_name = row ["keyspace_name" ]
@@ -2498,6 +2556,14 @@ def _schema_type_to_cql(type_string):
2498
2556
return type_string
2499
2557
2500
2558
2559
+ class SchemaParserDSE60 (SchemaParserV3 ):
2560
+ """
2561
+ For DSE 6.0+
2562
+ """
2563
+ recognized_table_options = (SchemaParserV3 .recognized_table_options +
2564
+ ("nodesync" ,))
2565
+
2566
+
2501
2567
class SchemaParserV4 (SchemaParserV3 ):
2502
2568
2503
2569
recognized_table_options = tuple (
@@ -2629,10 +2695,25 @@ def _build_keyspace_metadata_internal(row):
2629
2695
return super (SchemaParserV4 , SchemaParserV4 )._build_keyspace_metadata_internal (row )
2630
2696
2631
2697
2698
+ class SchemaParserDSE67 (SchemaParserV4 ):
2699
+ """
2700
+ For DSE 6.7+
2701
+ """
2702
+ recognized_table_options = (SchemaParserV4 .recognized_table_options +
2703
+ ("nodesync" ,))
2704
+
2705
+
2632
2706
class TableMetadataV3 (TableMetadata ):
2707
+ """
2708
+ For C* 3.0+. `option_maps` take a superset of map names, so if nothing
2709
+ changes structurally, new option maps can just be appended to the list.
2710
+ """
2633
2711
compaction_options = {}
2634
2712
2635
- option_maps = ['compaction' , 'compression' , 'caching' ]
2713
+ option_maps = [
2714
+ 'compaction' , 'compression' , 'caching' ,
2715
+ 'nodesync' # added DSE 6.0
2716
+ ]
2636
2717
2637
2718
@property
2638
2719
def is_cql_compatible (self ):
@@ -2768,11 +2849,18 @@ def export_as_string(self):
2768
2849
return self .as_cql_query (formatted = True ) + ";"
2769
2850
2770
2851
2771
- def get_schema_parser (connection , server_version , timeout ):
2852
+ def get_schema_parser (connection , server_version , dse_version , timeout ):
2772
2853
version = Version (server_version )
2854
+ if dse_version :
2855
+ v = Version (dse_version )
2856
+ if v >= Version ('6.7.0' ):
2857
+ return SchemaParserDSE67 (connection , timeout )
2858
+ elif v >= Version ('6.0.0' ):
2859
+ return SchemaParserDSE60 (connection , timeout )
2860
+
2773
2861
if version >= Version ('4.0.0' ):
2774
2862
return SchemaParserV4 (connection , timeout )
2775
- if version >= Version ('3.0.0' ):
2863
+ elif version >= Version ('3.0.0' ):
2776
2864
return SchemaParserV3 (connection , timeout )
2777
2865
else :
2778
2866
# we could further specialize by version. Right now just refactoring the
@@ -2791,6 +2879,14 @@ def _cql_from_cass_type(cass_type):
2791
2879
return cass_type .cql_parameterized_type ()
2792
2880
2793
2881
2882
+ class RLACTableExtension (RegisteredTableExtension ):
2883
+ name = "DSE_RLACA"
2884
+
2885
+ @classmethod
2886
+ def after_table_cql (cls , table_meta , ext_key , ext_blob ):
2887
+ return "RESTRICT ROWS ON %s.%s USING %s;" % (protect_name (table_meta .keyspace_name ),
2888
+ protect_name (table_meta .name ),
2889
+ protect_name (ext_blob .decode ('utf-8' )))
2794
2890
NO_VALID_REPLICA = object ()
2795
2891
2796
2892
0 commit comments