Browse code

kamctl/dbtextdb: solve TypeError: a bytes-like object is required, not 'str'

- GH #2469

Daniel-Constantin Mierla authored on 24/06/2022 06:18:42
Showing 1 changed files
... ...
@@ -1135,7 +1135,7 @@ class DBText(object):
1135 1135
             else:
1136 1136
                 header = '%s)' % header
1137 1137
 
1138
-        self.temp_file.write(header.strip() + '\n')
1138
+        self.temp_file.write((header.strip() + '\n').encode())
1139 1139
 
1140 1140
         # write data
1141 1141
         for row in self.data:
... ...
@@ -1143,7 +1143,7 @@ class DBText(object):
1143 1143
             for col in columns:
1144 1144
                 row_str = '%s:%s' % (row_str, row[col])
1145 1145
 
1146
-            self.temp_file.write(row_str[1:] + '\n')
1146
+            self.temp_file.write((row_str[1:] + '\n').encode())
1147 1147
 
1148 1148
         self.temp_file.flush()
1149 1149
 
Browse code

utils/kamctl: dbtextdb.py close previous opened file properly

related #1747

Victor Seva authored on 04/05/2020 13:22:36
Showing 1 changed files
... ...
@@ -60,6 +60,10 @@ class DBText(object):
60 60
         if not os.path.isdir(location):
61 61
             raise ParseError(location + ' is not a directory')
62 62
 
63
+    def __del__(self):
64
+        if getattr(self, 'fd', False):
65
+            self.fd.close()
66
+
63 67
     def _ParseOrderBy(self):
64 68
         """Parse out the column name to be used for ordering the dataset.
65 69
 
... ...
@@ -438,6 +442,8 @@ class DBText(object):
438 442
         self.command = ''     # which command are we executing
439 443
         self.strings = []     # list of string literals parsed from the query
440 444
         self.parens = []      # list of parentheses parsed from the query
445
+        if getattr(self, 'fd', False):
446
+            self.fd.close()
441 447
 
442 448
     def ParseQuery(self, query):
443 449
         """External wrapper for the query parsing routines.
Browse code

utils/kamctl: fix dbtestdb

some indentantion errors introduced at https://github.com/kamailio/kamailio/commit/bc8bdcaa726f375f6deea8228a1ab0cf97c67035

related #1747

Victor Seva authored on 04/05/2020 12:01:33
Showing 1 changed files
... ...
@@ -146,11 +146,11 @@ class DBText(object):
146 146
         # check if there is a function modifier on the columns
147 147
         if self.tokens[0] == 'COUNT':
148 148
             self.count = True
149
-        if col_end == 1:
150
-            raise ParseError('COUNT must be followed by column name[s]')
151
-        if not self.tokens[1].startswith(self._paren_placeholder):
152
-            raise ParseError('COUNT must be followed by ()')
153
-        cols_str = self._ReplaceParens(self.tokens[1])
149
+            if col_end == 1:
150
+                raise ParseError('COUNT must be followed by column name[s]')
151
+            if not self.tokens[1].startswith(self._paren_placeholder):
152
+                raise ParseError('COUNT must be followed by ()')
153
+            cols_str = self._ReplaceParens(self.tokens[1])
154 154
 
155 155
         cols = cols_str.split(',')
156 156
         for col in cols:
... ...
@@ -300,7 +300,7 @@ class DBText(object):
300 300
             if self.tokens.pop(0) != 'SET':
301 301
                 raise ParseError('UPDATE command must be followed by SET')
302 302
 
303
-        self.targets = self._ParsePairs(' '.join(self.tokens), ',')
303
+            self.targets = self._ParsePairs(' '.join(self.tokens), ',')
304 304
 
305 305
         # INSERT
306 306
         if self.command == 'INSERT':
... ...
@@ -570,19 +570,19 @@ class DBText(object):
570 570
                         string = '%s%s' % (string, c)
571 571
                         continue  # wait for matching delim
572 572
 
573
-                started -= 1
574
-                if not started:
575
-                    values.append(string)
576
-                    new_args = '%s %s' % (new_args, '%s%d' % (placeholder,
577
-                                                              my_id))
578
-                    my_id += 1
579
-                    string = ''
573
+                    started -= 1
574
+                    if not started:
575
+                        values.append(string)
576
+                        new_args = '%s %s' % (new_args, '%s%d' % (placeholder,
577
+                                                                  my_id))
578
+                        my_id += 1
579
+                        string = ''
580 580
 
581
-        else:
582
-            if not started:
583
-                new_args = '%s%s' % (new_args, c)
584 581
             else:
585
-                string = '%s%s' % (string, c)
582
+                if not started:
583
+                    new_args = '%s%s' % (new_args, c)
584
+                else:
585
+                    string = '%s%s' % (string, c)
586 586
 
587 587
         if started:
588 588
             if mode == 'parens':
... ...
@@ -714,8 +714,8 @@ class DBText(object):
714 714
             elif self.header[col]['auto']:
715 715
                 new_row[col] = self._GetNextAuto(col)
716 716
 
717
-        else:
718
-            raise ExecuteError(col + ' cannot be empty or null')
717
+            else:
718
+                raise ExecuteError(col + ' cannot be empty or null')
719 719
 
720 720
         self.data.append(new_row)
721 721
         return [1]
Browse code

utils/kamctl: fix handling of Exceptions

> Traceback (most recent call last):
> File "/usr/lib/x86_64-linux-gnu/kamailio/kamctl/dbtextdb/dbtextdb.py", line 1239, in <module>
> main(sys.argv)
> File "/usr/lib/x86_64-linux-gnu/kamailio/kamctl/dbtextdb/dbtextdb.py", line 1233, in main
> except (Error, e):
> NameError: name 'e' is not defined

Victor Seva authored on 04/05/2020 09:33:12
Showing 1 changed files
... ...
@@ -356,7 +356,7 @@ class DBText(object):
356 356
         # test that the value is string, if not return it as is
357 357
         try:
358 358
             value.find('a')
359
-        except:
359
+        except Exception:
360 360
             return value
361 361
 
362 362
         escaped = value
... ...
@@ -377,7 +377,7 @@ class DBText(object):
377 377
         # test that the value is string, if not return it as is
378 378
         try:
379 379
             value.find('a')
380
-        except:
380
+        except Exception:
381 381
             return value
382 382
 
383 383
         escaped = value
... ...
@@ -988,21 +988,19 @@ class DBText(object):
988 988
         if not val and not self.header[col]['null']:
989 989
             raise ExecuteError(col + ' cannot be empty or null')
990 990
 
991
-        if (self.header[col]['type'].lower() == 'int' or
992
-           self.header[col]['type'].lower() == 'double'):
991
+        hdr_t = self.header[col]['type'].lower()
992
+        if hdr_t == 'int' or hdr_t == 'double':
993 993
             try:
994 994
                 if val:
995 995
                     val = eval(val)
996
-            except (NameError, e):
996
+            except NameError as e:
997 997
                 raise ExecuteError('Failed to parse %s in %s '
998 998
                                    '(unable to convert to type %s): %s' %
999
-                                   (col, self.table, self.header[col]['type'],
1000
-                                    e))
1001
-            except (SyntaxError, e):
999
+                                   (col, self.table, hdr_t, e))
1000
+            except SyntaxError as e:
1002 1001
                 raise ExecuteError('Failed to parse %s in %s '
1003 1002
                                    '(unable to convert to type %s): %s' %
1004
-                                   (col, self.table, self.header[col]['type'],
1005
-                                    e))
1003
+                                   (col, self.table, hdr_t, e))
1006 1004
 
1007 1005
         return val
1008 1006
 
... ...
@@ -1083,7 +1081,7 @@ class DBText(object):
1083 1081
             # save a copy of the data before modifying
1084 1082
             self.orig_data = self.data[:]
1085 1083
 
1086
-        except (IOError, e):
1084
+        except IOError as e:
1087 1085
             raise ExecuteError('Unable to open table %s: %s' % (self.table, e))
1088 1086
 
1089 1087
         Debug('Header is: %s' % self.header)
... ...
@@ -1230,7 +1228,7 @@ def main(argv):
1230 1228
                     print('Updated %s, rows affected: %d' % (conn.table, row))
1231 1229
                 else:
1232 1230
                     print(row)
1233
-    except (Error, e):
1231
+    except Error as e:
1234 1232
         print(e)
1235 1233
         sys.exit(1)
1236 1234
 
Browse code

utils/kamctl: update shebang to python3

Victor Seva authored on 22/10/2019 08:02:36
Showing 1 changed files
... ...
@@ -1,4 +1,4 @@
1
-#!/usr/bin/python
1
+#!/usr/bin/python3
2 2
 #
3 3
 # Copyright 2008 Google Inc. All Rights Reserved.
4 4
 
Browse code

kamctl: Update dbtextdb.py for Python3 - reported by GH #1747

Anthony Messina authored on 29/09/2019 16:04:58
Showing 1 changed files
... ...
@@ -9,8 +9,6 @@ using basic SQL syntax thus avoiding special case handling of dbtext.
9 9
 
10 10
 """
11 11
 
12
-__author__ = 'herman@google.com (Herman Sheremetyev)'
13
-
14 12
 import fcntl
15 13
 import os
16 14
 import shutil
... ...
@@ -18,1182 +16,1224 @@ import sys
18 16
 import tempfile
19 17
 import time
20 18
 
19
+__author__ = 'herman@google.com (Herman Sheremetyev)'
20
+
21 21
 if 'DBTEXTDB_DEBUG' in os.environ:
22
-  DEBUG = os.environ['DBTEXTDB_DEBUG']
22
+    DEBUG = os.environ['DBTEXTDB_DEBUG']
23 23
 else:
24
-  DEBUG = 0
24
+    DEBUG = 0
25 25
 
26 26
 
27 27
 def Debug(msg):
28
-  """Debug print method."""
29
-  if DEBUG:
30
-    print msg
28
+    """Debug print method."""
29
+    if DEBUG:
30
+        print(msg)
31 31
 
32 32
 
33 33
 class DBText(object):
34
-  """Provides connection to a dbtext database."""
35
-
36
-  RESERVED_WORDS = ['SELECT', 'DELETE', 'UPDATE', 'INSERT', 'SET',
37
-                    'VALUES', 'INTO', 'FROM', 'ORDER', 'BY', 'WHERE',
38
-                    'COUNT', 'CONCAT', 'AND', 'AS']
39
-  ALL_COMMANDS = ['SELECT', 'DELETE', 'UPDATE', 'INSERT']
40
-  WHERE_COMMANDS = ['SELECT', 'DELETE', 'UPDATE']
41
-
42
-  def __init__(self, location):
43
-    self.location = location  # location of dbtext tables
44
-    self.tokens = []          # query broken up into tokens
45
-    self.conditions = {}      # args to the WHERE clause
46
-    self.columns = []         # columns requested by SELECT
47
-    self.table = ''           # name of the table being queried
48
-    self.header = {}          # table header
49
-    self.orig_data = []       # original table data used to diff after updates
50
-    self.data = []            # table data as a list of dicts
51
-    self.count = False        # where or not using COUNT()
52
-    self.aliases = {}         # column aliases (SELECT AS)
53
-    self.targets = {}         # target columns-value pairs for INSERT/UPDATE
54
-    self.args = ''            # query arguments preceeding the ;
55
-    self.command = ''         # which command are we executing
56
-    self.strings = []         # list of string literals parsed from the query
57
-    self.parens = []          # list of parentheses parsed from the query
58
-    self._str_placeholder = '__DBTEXTDB_PARSED_OUT_STRING__'
59
-    self._paren_placeholder = '__DBTEXTDB_PARSED_OUT_PARENS__'
60
-    if not os.path.isdir(location):
61
-      raise ParseError(location + ' is not a directory')
62
-
63
-  def _ParseOrderBy(self):
64
-    """Parse out the column name to be used for ordering the dataset.
65
-
66
-    Raises:
67
-      ParseError: Invalid ORDER BY clause
68
-    """
69
-    self.order_by = ''
70
-    if 'ORDER' in self.tokens:
71
-      order_index = self.tokens.index('ORDER')
72
-      if order_index != len(self.tokens) - 3:
73
-        raise ParseError('ORDER must be followed with BY and column name')
74
-      if self.tokens[order_index + 1] != 'BY':
75
-        raise ParseError('ORDER must be followed with BY')
76
-      self.order_by = self.tokens[order_index + 2]
77
-
78
-      # strip off the order by stuff
79
-      self.tokens.pop()  # column name
80
-      self.tokens.pop()  # BY
81
-      self.tokens.pop()  # ORDER
82
-
83
-    elif 'BY' in self.tokens:
84
-      raise ParseError('BY must be preceeded by ORDER')
85
-
86
-    Debug('Order by: ' + self.order_by)
87
-
88
-  def _ParseConditions(self):
89
-    """Parse out WHERE clause.
90
-
91
-    Take everything after the WHERE keyword and convert it to a dict of
92
-    name value pairs corresponding to the columns and their values that
93
-    should be matched.
94
-
95
-    Raises:
96
-      ParseError: Invalid WHERE clause
97
-      NotSupportedError: Unsupported syntax
98
-    """
99
-    self.conditions = {}
100
-    Debug('self.tokens = %s' % self.tokens)
101
-    if 'WHERE' not in self.tokens:
102
-      return
103
-
104
-    if self.command not in self.WHERE_COMMANDS:
105
-      raise ParseError(self.command + ' cannot have a WHERE clause')
106
-    if 'OR' in self.tokens:
107
-      raise NotSupportedError('WHERE clause does not support OR operator')
108
-
109
-    where_clause = self.tokens[self.tokens.index('WHERE') + 1:]
110
-    self.conditions = self._ParsePairs(' '.join(where_clause), 'AND')
111
-    for cond in self.conditions:
112
-      self.conditions[cond] = self._EscapeChars(self.conditions[cond])
113
-    Debug('Conditions are [%s]' % self.conditions)
114
-
115
-    # pop off where clause
116
-    a = self.tokens.pop()
117
-    while a != 'WHERE':
118
-      a = self.tokens.pop()
119
-
120
-    Debug('self.tokens: %s' % self.tokens)
121
-
122
-  def _ParseColumns(self):
123
-    """Parse out the columns that need to be selected.
124
-
125
-    Raises:
126
-      ParseError: Invalid SELECT syntax
127
-    """
128
-    self.columns = []
129
-    self.count = False
130
-    self.aliases = {}
131
-    col_end = 0
132
-    # this is only valid for SELECT
133
-    if self.command != 'SELECT':
134
-      return
135
-
136
-    if 'FROM' not in self.tokens:
137
-      raise ParseError('SELECT must be followed by FROM')
138
-
139
-    col_end = self.tokens.index('FROM')
140
-    if not col_end:  # col_end == 0
141
-      raise ParseError('SELECT must be followed by column name[s]')
142
-
143
-    cols_str = ' '.join(self.tokens[0:col_end])
144
-    # check if there is a function modifier on the columns
145
-    if self.tokens[0] == 'COUNT':
146
-      self.count = True
147
-      if col_end == 1:
148
-        raise ParseError('COUNT must be followed by column name[s]')
149
-      if not self.tokens[1].startswith(self._paren_placeholder):
150
-        raise ParseError('COUNT must be followed by ()')
151
-      cols_str = self._ReplaceParens(self.tokens[1])
152
-
153
-    cols = cols_str.split(',')
154
-    for col in cols:
155
-      if not col.strip():
156
-        raise ParseError('Extra comma in columns')
157
-      col_split = col.split()
158
-      if col_split[0] == 'CONCAT':
159
-        # found a concat statement, do the same overall steps for those cols
160
-        self._ParseColumnsConcatHelper(col_split)
161
-      else:
162
-        col_split = col.split()
163
-        if len(col_split) > 2 and col_split[1] != 'AS':
164
-          raise ParseError('multiple columns must be separated by a comma')
165
-        elif len(col_split) == 3:
166
-          if col_split[1] != 'AS':
167
-            raise ParseError('Invalid column alias, use AS')
168
-          my_key = self._ReplaceStringLiterals(col_split[2], quotes=True)
169
-          my_val = self._ReplaceStringLiterals(col_split[0], quotes=True)
170
-          self.aliases[my_key] = [my_val]
171
-          self.columns.append(my_key)
172
-        elif len(col_split) > 3:
173
-          raise ParseError('multiple columns must be separated by a comma')
174
-        elif len(col_split) == 2:  # alias
175
-          my_key = self._ReplaceStringLiterals(col_split[1], quotes=True)
176
-          my_val = self._ReplaceStringLiterals(col_split[0], quotes=True)
177
-          self.aliases[my_key] = [my_val]
178
-          self.columns.append(my_key)
179
-        else:
180
-          col = self._ReplaceStringLiterals(col, quotes=True).strip()
181
-          if not col:  # col == ''
182
-            raise ParseError('empty column name not allowed')
183
-
184
-          self.columns.append(col)
185
-
186
-    # pop off all the columns related junk
187
-    self.tokens = self.tokens[col_end + 1:]
188
-
189
-    Debug('Columns: %s' % self.columns)
190
-    Debug('Aliases: %s' % self.aliases)
191
-    Debug('self.tokens: %s' % self.tokens)
192
-
193
-  def _ParseColumnsConcatHelper(self, col_split):
194
-    """Handles the columns being CONCAT'd together.
195
-
196
-    Args:
197
-      col_split: ['column', 'column']
198
-
199
-    Raises:
200
-      ParseError: invalid CONCAT()
201
-    """
202
-    concat_placeholder = '_'
203
-    split_len = len(col_split)
204
-    if split_len == 1:
205
-      raise ParseError('CONCAT() must be followed by column name[s]')
206
-    if not col_split[1].startswith(self._paren_placeholder):
207
-      raise ParseError('CONCAT must be followed by ()')
208
-    if split_len > 2:
209
-      if split_len == 4 and col_split[2] != 'AS':
210
-        raise ParseError('CONCAT() must be followed by an AS clause')
211
-      if split_len > 5:
212
-        raise ParseError('CONCAT() AS clause takes exactly 1 arg. '
213
-                         'Extra args: [%s]' % (col_split[4:]))
214
-      else:
215
-        concat_placeholder = self._ReplaceStringLiterals(col_split[-1],
34
+    """Provides connection to a dbtext database."""
35
+
36
+    RESERVED_WORDS = ['SELECT', 'DELETE', 'UPDATE', 'INSERT', 'SET',
37
+                      'VALUES', 'INTO', 'FROM', 'ORDER', 'BY', 'WHERE',
38
+                      'COUNT', 'CONCAT', 'AND', 'AS']
39
+    ALL_COMMANDS = ['SELECT', 'DELETE', 'UPDATE', 'INSERT']
40
+    WHERE_COMMANDS = ['SELECT', 'DELETE', 'UPDATE']
41
+
42
+    def __init__(self, location):
43
+        self.location = location  # location of dbtext tables
44
+        self.tokens = []      # query broken up into tokens
45
+        self.conditions = {}  # args to the WHERE clause
46
+        self.columns = []     # columns requested by SELECT
47
+        self.table = ''       # name of the table being queried
48
+        self.header = {}      # table header
49
+        self.orig_data = []   # original table data used to diff after updates
50
+        self.data = []        # table data as a list of dicts
51
+        self.count = False    # where or not using COUNT()
52
+        self.aliases = {}     # column aliases (SELECT AS)
53
+        self.targets = {}     # target columns-value pairs for INSERT/UPDATE
54
+        self.args = ''        # query arguments preceeding the ;
55
+        self.command = ''     # which command are we executing
56
+        self.strings = []     # list of string literals parsed from the query
57
+        self.parens = []      # list of parentheses parsed from the query
58
+        self._str_placeholder = '__DBTEXTDB_PARSED_OUT_STRING__'
59
+        self._paren_placeholder = '__DBTEXTDB_PARSED_OUT_PARENS__'
60
+        if not os.path.isdir(location):
61
+            raise ParseError(location + ' is not a directory')
62
+
63
+    def _ParseOrderBy(self):
64
+        """Parse out the column name to be used for ordering the dataset.
65
+
66
+        Raises:
67
+            ParseError: Invalid ORDER BY clause
68
+        """
69
+        self.order_by = ''
70
+        if 'ORDER' in self.tokens:
71
+            order_index = self.tokens.index('ORDER')
72
+            if order_index != len(self.tokens) - 3:
73
+                raise ParseError('ORDER must be followed with BY and column '
74
+                                 'name')
75
+            if self.tokens[order_index + 1] != 'BY':
76
+                raise ParseError('ORDER must be followed with BY')
77
+            self.order_by = self.tokens[order_index + 2]
78
+
79
+            # strip off the order by stuff
80
+            self.tokens.pop()  # column name
81
+            self.tokens.pop()  # BY
82
+            self.tokens.pop()  # ORDER
83
+
84
+        elif 'BY' in self.tokens:
85
+            raise ParseError('BY must be preceeded by ORDER')
86
+
87
+        Debug('Order by: ' + self.order_by)
88
+
89
+    def _ParseConditions(self):
90
+        """Parse out WHERE clause.
91
+
92
+        Take everything after the WHERE keyword and convert it to a dict of
93
+        name value pairs corresponding to the columns and their values that
94
+        should be matched.
95
+
96
+        Raises:
97
+            ParseError: Invalid WHERE clause
98
+            NotSupportedError: Unsupported syntax
99
+        """
100
+        self.conditions = {}
101
+        Debug('self.tokens = %s' % self.tokens)
102
+        if 'WHERE' not in self.tokens:
103
+            return
104
+
105
+        if self.command not in self.WHERE_COMMANDS:
106
+            raise ParseError(self.command + ' cannot have a WHERE clause')
107
+        if 'OR' in self.tokens:
108
+            raise NotSupportedError('WHERE clause does not support OR '
109
+                                    'operator')
110
+
111
+        where_clause = self.tokens[self.tokens.index('WHERE') + 1:]
112
+        self.conditions = self._ParsePairs(' '.join(where_clause), 'AND')
113
+        for cond in self.conditions:
114
+            self.conditions[cond] = self._EscapeChars(self.conditions[cond])
115
+        Debug('Conditions are [%s]' % self.conditions)
116
+
117
+        # pop off where clause
118
+        a = self.tokens.pop()
119
+        while a != 'WHERE':
120
+            a = self.tokens.pop()
121
+
122
+        Debug('self.tokens: %s' % self.tokens)
123
+
124
+    def _ParseColumns(self):
125
+        """Parse out the columns that need to be selected.
126
+
127
+        Raises:
128
+            ParseError: Invalid SELECT syntax
129
+        """
130
+        self.columns = []
131
+        self.count = False
132
+        self.aliases = {}
133
+        col_end = 0
134
+        # this is only valid for SELECT
135
+        if self.command != 'SELECT':
136
+            return
137
+
138
+        if 'FROM' not in self.tokens:
139
+            raise ParseError('SELECT must be followed by FROM')
140
+
141
+        col_end = self.tokens.index('FROM')
142
+        if not col_end:  # col_end == 0
143
+            raise ParseError('SELECT must be followed by column name[s]')
144
+
145
+        cols_str = ' '.join(self.tokens[0:col_end])
146
+        # check if there is a function modifier on the columns
147
+        if self.tokens[0] == 'COUNT':
148
+            self.count = True
149
+        if col_end == 1:
150
+            raise ParseError('COUNT must be followed by column name[s]')
151
+        if not self.tokens[1].startswith(self._paren_placeholder):
152
+            raise ParseError('COUNT must be followed by ()')
153
+        cols_str = self._ReplaceParens(self.tokens[1])
154
+
155
+        cols = cols_str.split(',')
156
+        for col in cols:
157
+            if not col.strip():
158
+                raise ParseError('Extra comma in columns')
159
+            col_split = col.split()
160
+            if col_split[0] == 'CONCAT':
161
+                # found a concat statement, do the same overall steps
162
+                # for those cols
163
+                self._ParseColumnsConcatHelper(col_split)
164
+            else:
165
+                col_split = col.split()
166
+                if len(col_split) > 2 and col_split[1] != 'AS':
167
+                    raise ParseError('multiple columns must be separated '
168
+                                     'by a comma')
169
+                elif len(col_split) == 3:
170
+                    if col_split[1] != 'AS':
171
+                        raise ParseError('Invalid column alias, use AS')
172
+                    my_key = self._ReplaceStringLiterals(col_split[2],
173
+                                                         quotes=True)
174
+                    my_val = self._ReplaceStringLiterals(col_split[0],
175
+                                                         quotes=True)
176
+                    self.aliases[my_key] = [my_val]
177
+                    self.columns.append(my_key)
178
+                elif len(col_split) > 3:
179
+                    raise ParseError('multiple columns must be separated by '
180
+                                     'a comma')
181
+                elif len(col_split) == 2:  # alias
182
+                    my_key = self._ReplaceStringLiterals(col_split[1],
183
+                                                         quotes=True)
184
+                    my_val = self._ReplaceStringLiterals(col_split[0],
216 185
                                                          quotes=True)
186
+                    self.aliases[my_key] = [my_val]
187
+                    self.columns.append(my_key)
188
+                else:
189
+                    col = self._ReplaceStringLiterals(col, quotes=True).strip()
190
+                    if not col:  # col == ''
191
+                        raise ParseError('empty column name not allowed')
192
+
193
+                    self.columns.append(col)
194
+
195
+        # pop off all the columns related junk
196
+        self.tokens = self.tokens[col_end + 1:]
197
+
198
+        Debug('Columns: %s' % self.columns)
199
+        Debug('Aliases: %s' % self.aliases)
200
+        Debug('self.tokens: %s' % self.tokens)
201
+
202
+    def _ParseColumnsConcatHelper(self, col_split):
203
+        """Handles the columns being CONCAT'd together.
204
+
205
+        Args:
206
+            col_split: ['column', 'column']
207
+
208
+        Raises:
209
+            ParseError: invalid CONCAT()
210
+        """
211
+        concat_placeholder = '_'
212
+        split_len = len(col_split)
213
+        if split_len == 1:
214
+            raise ParseError('CONCAT() must be followed by column name[s]')
215
+        if not col_split[1].startswith(self._paren_placeholder):
216
+            raise ParseError('CONCAT must be followed by ()')
217
+        if split_len > 2:
218
+            if split_len == 4 and col_split[2] != 'AS':
219
+                raise ParseError('CONCAT() must be followed by an AS clause')
220
+            if split_len > 5:
221
+                raise ParseError('CONCAT() AS clause takes exactly 1 arg. '
222
+                                 'Extra args: [%s]' % (col_split[4:]))
223
+            else:
224
+                concat_placeholder = self._ReplaceStringLiterals(col_split[-1],
225
+                                                                 quotes=True)
226
+
227
+        # make sure this place hodler is unique
228
+        while concat_placeholder in self.aliases:
229
+            concat_placeholder += '_'
230
+        concat_cols_str = self._ReplaceParens(col_split[1])
231
+        concat_cols = concat_cols_str.split(',')
232
+        concat_col_list = []
233
+        for concat_col in concat_cols:
234
+            if ' ' in concat_col.strip():
235
+                raise ParseError('multiple columns must be separated by a '
236
+                                 'comma inside CONCAT()')
237
+            concat_col = self._ReplaceStringLiterals(concat_col,
238
+                                                     quotes=True).strip()
239
+            if not concat_col:
240
+                raise ParseError('Attempting to CONCAT empty set')
241
+            concat_col_list.append(concat_col)
242
+
243
+        self.aliases[concat_placeholder] = concat_col_list
244
+        self.columns.append(concat_placeholder)
245
+
246
+    def _ParseTable(self):
247
+        """Parse out the table name (multiple table names not supported).
248
+
249
+        Raises:
250
+        ParseError: Unable to parse table name
251
+        """
252
+        table_name = ''
253
+        if (not self.tokens or  # len == 0
254
+            (self.tokens[0] in self.RESERVED_WORDS and
255
+             self.tokens[0] not in ['FROM', 'INTO'])):
256
+            raise ParseError('Missing table name')
257
+
258
+        # SELECT
259
+        if self.command == 'SELECT':
260
+            table_name = self.tokens.pop(0)
261
+
262
+        # INSERT
263
+        elif self.command == 'INSERT':
264
+            table_name = self.tokens.pop(0)
265
+            if table_name == 'INTO':
266
+                table_name = self.tokens.pop(0)
267
+
268
+        # DELETE
269
+        elif self.command == 'DELETE':
270
+            if self.tokens[0] != 'FROM':
271
+                raise ParseError('DELETE command must be followed by FROM')
272
+
273
+            self.tokens.pop(0)  # FROM
274
+            table_name = self.tokens.pop(0)
275
+
276
+        # UPDATE
277
+        elif self.command == 'UPDATE':
278
+            table_name = self.tokens.pop(0)
279
+
280
+        if not self.table:
281
+            self.table = table_name
217 282
 
218
-    # make sure this place hodler is unique
219
-    while concat_placeholder in self.aliases:
220
-      concat_placeholder += '_'
221
-    concat_cols_str = self._ReplaceParens(col_split[1])
222
-    concat_cols = concat_cols_str.split(',')
223
-    concat_col_list = []
224
-    for concat_col in concat_cols:
225
-      if ' ' in concat_col.strip():
226
-        raise ParseError('multiple columns must be separated by a'
227
-                         ' comma inside CONCAT()')
228
-      concat_col = self._ReplaceStringLiterals(concat_col, quotes=True).strip()
229
-      if not concat_col:
230
-        raise ParseError('Attempting to CONCAT empty set')
231
-      concat_col_list.append(concat_col)
232
-
233
-    self.aliases[concat_placeholder] = concat_col_list
234
-    self.columns.append(concat_placeholder)
235
-
236
-  def _ParseTable(self):
237
-    """Parse out the table name (multiple table names not supported).
238
-
239
-    Raises:
240
-      ParseError: Unable to parse table name
241
-    """
242
-    table_name = ''
243
-    if (not self.tokens or  # len == 0
244
-        (self.tokens[0] in self.RESERVED_WORDS and
245
-         self.tokens[0] not in ['FROM', 'INTO'])):
246
-      raise ParseError('Missing table name')
247
-
248
-    # SELECT
249
-    if self.command == 'SELECT':
250
-      table_name = self.tokens.pop(0)
251
-
252
-    # INSERT
253
-    elif self.command == 'INSERT':
254
-      table_name = self.tokens.pop(0)
255
-      if table_name == 'INTO':
256
-        table_name = self.tokens.pop(0)
257
-
258
-    # DELETE
259
-    elif self.command == 'DELETE':
260
-      if self.tokens[0] != 'FROM':
261
-        raise ParseError('DELETE command must be followed by FROM')
262
-
263
-      self.tokens.pop(0)  # FROM
264
-      table_name = self.tokens.pop(0)
265
-
266
-    # UPDATE
267
-    elif self.command == 'UPDATE':
268
-      table_name = self.tokens.pop(0)
269
-
270
-    if not self.table:
271
-      self.table = table_name
272
-
273
-    else:  # multiple queries detected, make sure they're against same table
274
-      if self.table != table_name:
275
-        raise ParseError('Table changed between queries! %s -> %s' %
276
-                         (self.table, table_name))
277
-    Debug('Table is [%s]' % self.table)
278
-    Debug('self.tokens is %s' % self.tokens)
279
-
280
-  def _ParseTargets(self):
281
-    """Parse out name value pairs of columns and their values.
282
-
283
-    Raises:
284
-      ParseError: Unable to parse targets
285
-    """
286
-    self.targets = {}
287
-    # UPDATE
288
-    if self.command == 'UPDATE':
289
-      if self.tokens.pop(0) != 'SET':
290
-        raise ParseError('UPDATE command must be followed by SET')
291
-
292
-      self.targets = self._ParsePairs(' '.join(self.tokens), ',')
293
-
294
-    # INSERT
295
-    if self.command == 'INSERT':
296
-      if self.tokens[0] == 'SET':
297
-        self.targets = self._ParsePairs(' '.join(self.tokens[1:]), ',')
298
-
299
-      elif len(self.tokens) == 3 and self.tokens[1] == 'VALUES':
300
-        if not self.tokens[0].startswith(self._paren_placeholder):
301
-          raise ParseError('INSERT column names must be inside parens')
302
-        if not self.tokens[2].startswith(self._paren_placeholder):
303
-          raise ParseError('INSERT values must be inside parens')
304
-
305
-        cols = self._ReplaceParens(self.tokens[0]).split(',')
306
-        vals = self._ReplaceParens(self.tokens[2]).split(',')
307
-
308
-        if len(cols) != len(vals):
309
-          raise ParseError('INSERT column and value numbers must match')
310
-        if not cols:  # len == 0
311
-          raise ParseError('INSERT column number must be greater than 0')
283
+        else:
284
+            # multiple queries detected, make sure they're against same table
285
+            if self.table != table_name:
286
+                raise ParseError('Table changed between queries! %s -> %s' %
287
+                                 (self.table, table_name))
288
+        Debug('Table is [%s]' % self.table)
289
+        Debug('self.tokens is %s' % self.tokens)
290
+
291
+    def _ParseTargets(self):
292
+        """Parse out name value pairs of columns and their values.
293
+
294
+        Raises:
295
+            ParseError: Unable to parse targets
296
+        """
297
+        self.targets = {}
298
+        # UPDATE
299
+        if self.command == 'UPDATE':
300
+            if self.tokens.pop(0) != 'SET':
301
+                raise ParseError('UPDATE command must be followed by SET')
302
+
303
+        self.targets = self._ParsePairs(' '.join(self.tokens), ',')
304
+
305
+        # INSERT
306
+        if self.command == 'INSERT':
307
+            if self.tokens[0] == 'SET':
308
+                self.targets = self._ParsePairs(' '.join(self.tokens[1:]), ',')
309
+
310
+            elif len(self.tokens) == 3 and self.tokens[1] == 'VALUES':
311
+                if not self.tokens[0].startswith(self._paren_placeholder):
312
+                    raise ParseError('INSERT column names must be inside '
313
+                                     'parens')
314
+                if not self.tokens[2].startswith(self._paren_placeholder):
315
+                    raise ParseError('INSERT values must be inside parens')
316
+
317
+                cols = self._ReplaceParens(self.tokens[0]).split(',')
318
+                vals = self._ReplaceParens(self.tokens[2]).split(',')
319
+
320
+                if len(cols) != len(vals):
321
+                    raise ParseError('INSERT column and value numbers must '
322
+                                     'match')
323
+                if not cols:  # len == 0
324
+                    raise ParseError('INSERT column number must be greater '
325
+                                     'than 0')
326
+
327
+                i = 0
328
+                while i < len(cols):
329
+                    val = vals[i].strip()
330
+                    if not val:  # val == ''
331
+                        raise ParseError('INSERT values cannot be empty')
332
+                    if ' ' in val:
333
+                        raise ParseError('INSERT values must be comma '
334
+                                         'separated')
335
+                    self.targets[cols[i].strip()] = \
336
+                        self._ReplaceStringLiterals(val)
337
+                    i += 1
338
+
339
+            else:
340
+                raise ParseError('Unable to parse INSERT targets')
312 341
 
342
+        for target in self.targets:
343
+            self.targets[target] = self._EscapeChars(self.targets[target])
344
+
345
+        Debug('Targets are [%s]' % self.targets)
346
+
347
+    def _EscapeChars(self, value):
348
+        """Escape necessary chars before inserting into dbtext.
349
+
350
+        Args:
351
+            value: 'string'
352
+
353
+        Returns:
354
+            escaped: 'string' with chars escaped appropriately
355
+        """
356
+        # test that the value is string, if not return it as is
357
+        try:
358
+            value.find('a')
359
+        except:
360
+            return value
361
+
362
+        escaped = value
363
+        escaped = escaped.replace('\\', '\\\\').replace('\0', '\\0')
364
+        escaped = escaped.replace(':', '\\:').replace('\n', '\\n')
365
+        escaped = escaped.replace('\r', '\\r').replace('\t', '\\t')
366
+        return escaped
367
+
368
+    def _UnEscapeChars(self, value):
369
+        """Un-escape necessary chars before returning to user.
370
+
371
+        Args:
372
+            value: 'string'
373
+
374
+        Returns:
375
+            escaped: 'string' with chars escaped appropriately
376
+        """
377
+        # test that the value is string, if not return it as is
378
+        try:
379
+            value.find('a')
380
+        except:
381
+            return value
382
+
383
+        escaped = value
384
+        escaped = escaped.replace('\\:', ':').replace('\\n', '\n')
385
+        escaped = escaped.replace('\\r', '\r').replace('\\t', '\t')
386
+        escaped = escaped.replace('\\0', '\0').replace('\\\\', '\\')
387
+        return escaped
388
+
389
+    def Execute(self, query, writethru=True):
390
+        """Parse and execute the query.
391
+
392
+        Args:
393
+            query: e.g. 'select * from table;'
394
+            writethru: bool
395
+
396
+        Returns:
397
+            dataset: [{col: val, col: val}, {col: val}, {col: val}]
398
+
399
+        Raises:
400
+            ExecuteError: unable to execute query
401
+        """
402
+        # parse the query
403
+        self.ParseQuery(query)
404
+
405
+        # get lock and execute the query
406
+        self.OpenTable()
407
+        Debug('Running ' + self.command)
408
+        dataset = []
409
+        if self.command == 'SELECT':
410
+            dataset = self._RunSelect()
411
+        elif self.command == 'UPDATE':
412
+            dataset = self._RunUpdate()
413
+        elif self.command == 'INSERT':
414
+            dataset = self._RunInsert()
415
+        elif self.command == 'DELETE':
416
+            dataset = self._RunDelete()
417
+
418
+        if self.command != 'SELECT' and writethru:
419
+            self.WriteTempTable()
420
+            self.MoveTableIntoPlace()
421
+
422
+        Debug(dataset)
423
+        return dataset
424
+
425
+    def CleanUp(self):
426
+        """Reset the internal variables (for multiple queries)."""
427
+        self.tokens = []      # query broken up into tokens
428
+        self.conditions = {}  # args to the WHERE clause
429
+        self.columns = []     # columns requested by SELECT
430
+        self.table = ''       # name of the table being queried
431
+        self.header = {}      # table header
432
+        self.orig_data = []   # original table data used to diff after updates
433
+        self.data = []        # table data as a list of dicts
434
+        self.count = False    # where or not using COUNT()
435
+        self.aliases = {}     # column aliases (SELECT AS)
436
+        self.targets = {}     # target columns-value pairs for INSERT/UPDATE
437
+        self.args = ''        # query arguments preceeding the ;
438
+        self.command = ''     # which command are we executing
439
+        self.strings = []     # list of string literals parsed from the query
440
+        self.parens = []      # list of parentheses parsed from the query
441
+
442
+    def ParseQuery(self, quer