1313# and to allow some customizations.
1414
1515from io import TextIOBase
16+ from typing import List
1617
17- from sqlparse import tokens
18- from sqlparse .keywords import SQL_REGEX
18+ from sqlparse import tokens , keywords
1919from sqlparse .utils import consume
2020
2121
22- class Lexer :
23- """Lexer
24- Empty class. Leaving for backwards-compatibility
25- """
22+ class _LexerSingletonMetaclass (type ):
23+ _lexer_instance = None
24+
25+ def __call__ (cls , * args , ** kwargs ):
26+ if _LexerSingletonMetaclass ._lexer_instance is None :
27+ _LexerSingletonMetaclass ._lexer_instance = super (
28+ _LexerSingletonMetaclass , cls
29+ ).__call__ (* args , ** kwargs )
30+ return _LexerSingletonMetaclass ._lexer_instance
31+
32+
33+ class Lexer (metaclass = _LexerSingletonMetaclass ):
34+ """The Lexer supports configurable syntax.
35+ To add support for additional keywords, use the `add_keywords` method."""
36+
37+ _SQL_REGEX : keywords .SQL_REGEX_TYPE
38+ _keywords : List [keywords .KEYWORDS_TYPE ]
39+
40+ def default_initialization (self ):
41+ """Initialize the lexer with default dictionaries.
42+ Useful if you need to revert custom syntax settings."""
43+ self .clear ()
44+ self .set_SQL_REGEX (keywords .SQL_REGEX )
45+ self .add_keywords (keywords .KEYWORDS_COMMON )
46+ self .add_keywords (keywords .KEYWORDS_ORACLE )
47+ self .add_keywords (keywords .KEYWORDS_PLPGSQL )
48+ self .add_keywords (keywords .KEYWORDS_HQL )
49+ self .add_keywords (keywords .KEYWORDS_MSACCESS )
50+ self .add_keywords (keywords .KEYWORDS )
51+
52+ def __init__ (self ):
53+ self .default_initialization ()
54+
55+ def clear (self ):
56+ """Clear all syntax configurations.
57+ Useful if you want to load a reduced set of syntax configurations."""
58+ self ._SQL_REGEX = []
59+ self ._keywords = []
60+
61+ def set_SQL_REGEX (self , SQL_REGEX : keywords .SQL_REGEX_TYPE ):
62+ """Set the list of regex that will parse the SQL."""
63+ self ._SQL_REGEX = SQL_REGEX
64+
65+ def add_keywords (self , keywords : keywords .KEYWORDS_TYPE ):
66+ """Add keyword dictionaries. Keywords are looked up in the same order
67+ that dictionaries were added."""
68+ self ._keywords .append (keywords )
69+
70+ def is_keyword (self , value ):
71+ """Checks for a keyword.
72+
73+ If the given value is in one of the KEYWORDS_* dictionary
74+ it's considered a keyword. Otherwise tokens.Name is returned.
75+ """
76+ val = value .upper ()
77+ for kwdict in self ._keywords :
78+ if val in kwdict :
79+ return kwdict [val ], value
80+ else :
81+ return tokens .Name , value
2682
27- @staticmethod
28- def get_tokens (text , encoding = None ):
83+ def get_tokens (self , text , encoding = None ):
2984 """
3085 Return an iterable of (tokentype, value) pairs generated from
3186 `text`. If `unfiltered` is set to `True`, the filtering mechanism
@@ -48,24 +103,26 @@ def get_tokens(text, encoding=None):
48103 text = text .decode (encoding )
49104 else :
50105 try :
51- text = text .decode (' utf-8' )
106+ text = text .decode (" utf-8" )
52107 except UnicodeDecodeError :
53- text = text .decode (' unicode-escape' )
108+ text = text .decode (" unicode-escape" )
54109 else :
55- raise TypeError ("Expected text or file-like object, got {!r}" .
56- format (type (text )))
110+ raise TypeError (
111+ "Expected text or file-like object, got {!r}"
112+ .format (type (text ))
113+ )
57114
58115 iterable = enumerate (text )
59116 for pos , char in iterable :
60- for rexmatch , action in SQL_REGEX :
117+ for rexmatch , action in self . _SQL_REGEX :
61118 m = rexmatch (text , pos )
62119
63120 if not m :
64121 continue
65122 elif isinstance (action , tokens ._TokenType ):
66123 yield action , m .group ()
67- elif callable ( action ) :
68- yield action (m .group ())
124+ elif action is keywords . PROCESS_AS_KEYWORD :
125+ yield self . is_keyword (m .group ())
69126
70127 consume (iterable , m .end () - pos - 1 )
71128 break
0 commit comments