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, query):
443
+        """External wrapper for the query parsing routines.
444
+
445
+        Args:
446
+            query: string
447
+
448
+        Raises:
449
+            ParseError: Unable to parse query
450
+        """
451
+        self.args = query.split(';')[0]
452
+        self._Tokenize()
453
+        self._ParseCommand()
454
+        self._ParseOrderBy()
455
+        self._ParseConditions()
456
+        self._ParseColumns()
457
+        self._ParseTable()
458
+        self._ParseTargets()
459
+
460
+    def _ParseCommand(self):
461
+        """Determine the command: SELECT, UPDATE, DELETE or INSERT.
462
+
463
+        Raises:
464
+            ParseError: unable to parse command
465
+        """
466
+        self.command = self.tokens[0]
467
+        # Check that command is valid
468
+        if self.command not in self.ALL_COMMANDS:
469
+            raise ParseError('Unsupported command: ' + self.command)
470
+
471
+        self.tokens.pop(0)
472
+        Debug('Command is: %s' % self.command)
473
+        Debug('self.tokens: %s' % self.tokens)
474
+
475
+    def _Tokenize(self):
476
+        """Turn the string query into a list of tokens.
477
+
478
+        Split on '(', ')', ' ', ';', '=' and ','.
479
+            In addition capitalize any SQL keywords found.
480
+        """
481
+        # horrible hack to handle now()
482
+        time_now = '%s' % int(time.time())
483
+        # round off the seconds for unittesting
484
+        time_now = time_now[0:-2] + '00'
485
+        while 'now()' in self.args.lower():
486
+            start = self.args.lower().find('now()')
487
+            self.args = ('%s%s%s' % (self.args[0:start], time_now,
488
+                                     self.args[start + 5:]))
489
+        # pad token separators with spaces
490
+        pad = self.args.replace('(', ' ( ').replace(')', ' ) ')
491
+        pad = pad.replace(',', ' , ').replace(';', ' ; ').replace('=', ' = ')
492
+        self.args = pad
493
+        # parse out all the blocks (string literals and parens)
494
+        self._ParseOutBlocks()
495
+        # split remaining into tokens
496
+        self.tokens = self.args.split()
497
+
498
+        # now capitalize
313 499
         i = 0
314
-        while i < len(cols):
315
-          val = vals[i].strip()
316
-          if not val:  # val == ''
317
-            raise ParseError('INSERT values cannot be empty')
318
-          if ' ' in val:
319
-            raise ParseError('INSERT values must be comma separated')
320
-          self.targets[cols[i].strip()] = self._ReplaceStringLiterals(val)
321
-          i += 1
500
+        while i < len(self.tokens):
501
+            if self.tokens[i].upper() in self.RESERVED_WORDS:
502
+                self.tokens[i] = self.tokens[i].upper()
503
+
504
+            i += 1
505
+
506
+        Debug('Tokens: %s' % self.tokens)
507
+
508
+    def _ParseOutBlocks(self):
509
+        """Parse out string literals and parenthesized values."""
510
+        self.strings = []
511
+        self.parens = []
512
+
513
+        # set str placeholder to a value that's not present in the string
514
+        while self._str_placeholder in self.args:
515
+            self._str_placeholder = '%s_' % self._str_placeholder
516
+
517
+        # set paren placeholder to a value that's not present in the string
518
+        while self._paren_placeholder in self.args:
519
+            self._paren_placeholder = '%s_' % self._paren_placeholder
520
+
521
+        self.strings = self._ParseOutHelper(self._str_placeholder, ["'", '"'],
522
+                                            'quotes')
523
+        self.parens = self._ParseOutHelper(self._paren_placeholder, ['(', ')'],
524
+                                           'parens')
525
+        Debug('Strings: %s' % self.strings)
526
+        Debug('Parens: %s' % self.parens)
527
+
528
+    def _ParseOutHelper(self, placeholder, delims, mode):
529
+        """Replace all text within delims with placeholders.
530
+
531
+        Args:
532
+            placeholder: string
533
+            delims: list of strings
534
+            mode: string
535
+                'parens': if there are 2 delims treat the first as opening
536
+                          and second as closing, such as with ( and )
537
+                'quotes': treat each delim as either opening or
538
+                          closing and require the same one to terminate the
539
+                          block, such as with ' and "
540
+
541
+        Returns:
542
+            list: [value1, value2, ...]
543
+
544
+        Raises:
545
+            ParseError: unable to parse out delims
546
+            ExecuteError: Invalid usage
547
+        """
548
+        if mode not in ['quotes', 'parens']:
549
+            raise ExecuteError('_ParseOutHelper: invalid mode ' + mode)
550
+        if mode == 'parens' and len(delims) != 2:
551
+            raise ExecuteError('_ParseOutHelper: delims must have 2 values '
552
+                               'in "parens" mode')
553
+        values = []
554
+        started = 0
555
+        new_args = ''
556
+        string = ''
557
+        my_id = 0
558
+        delim = ''
559
+        for c in self.args:
560
+            if c in delims:
561
+                if not started:
562
+                    if mode == 'parens' and c != delims[0]:
563
+                        raise ParseError('Found closing delimeter %s before '
564
+                                         'corresponding %s' % (c, delims[0]))
565
+                    started += 1
566
+                    delim = c
567
+                else:
568
+                    if ((mode == 'parens' and c == delim) or
569
+                       (mode == 'quotes' and c != delim)):
570
+                        string = '%s%s' % (string, c)
571
+                        continue  # wait for matching delim
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 = ''
322 580
 
323
-      else:
324
-        raise ParseError('Unable to parse INSERT targets')
581
+        else:
582
+            if not started:
583
+                new_args = '%s%s' % (new_args, c)
584
+            else:
585
+                string = '%s%s' % (string, c)
586
+
587
+        if started:
588
+            if mode == 'parens':
589
+                waiting_for = delims[1]
590
+            else:
591
+                waiting_for = delim
592
+            raise ParseError('Unterminated block, waiting for ' + waiting_for)
593
+
594
+        self.args = new_args
595
+        Debug('Values: %s' % values)
596
+        return values
597
+
598
+    def _ReplaceStringLiterals(self, s, quotes=False):
599
+        """Replaces string placeholders with real values.
600
+
601
+            If quotes is set to True surround the returned value with single
602
+            quotes
603
+
604
+        Args:
605
+            s: string
606
+            quotes: bool
607
+
608
+        Returns:
609
+            s: string
610
+        """
611
+        if s.strip().startswith(self._str_placeholder):
612
+            str_index = int(s.split(self._str_placeholder)[1])
613
+            s = self.strings[str_index]
614
+            if quotes:
615
+                s = "'" + s + "'"
616
+
617
+        return s
618
+
619
+    def _ReplaceParens(self, s):
620
+        """Replaces paren placeholders with real values.
621
+
622
+        Args:
623
+            s: string
624
+
625
+        Returns:
626
+            s: string
627
+        """
628
+        if s.strip().startswith(self._paren_placeholder):
629
+            str_index = int(s.split(self._paren_placeholder)[1])
630
+            s = self.parens[str_index].strip()
631
+
632
+        return s
633
+
634
+    def _RunDelete(self):
635
+        """Run the DELETE command.
636
+
637
+        Go through the rows in self.data matching them against the conditions,
638
+        if they fit delete the row leaving a placeholder value (in order to
639
+        keep the iteration process sane).  Afterward clean up any empty values.
640
+
641
+        Returns:
642
+            dataset: [number of affected rows]
643
+        """
644
+        i = 0
645
+        length = len(self.data)
646
+        affected = 0
647
+        while i < length:
648
+            if self._MatchRow(self.data[i]):
649
+                self.data[i] = None
650
+                affected += 1
325 651
 
326
-    for target in self.targets:
327
-      self.targets[target] = self._EscapeChars(self.targets[target])
652
+            i += 1
328 653
 
329
-    Debug('Targets are [%s]' % self.targets)
654
+        # clean out the placeholders
655
+        while None in self.data:
656
+            self.data.remove(None)
330 657
 
331
-  def _EscapeChars(self, value):
332
-    """Escape necessary chars before inserting into dbtext.
658
+        return [affected]
333 659
 
334
-    Args:
335
-      value: 'string'
660
+    def _RunUpdate(self):
661
+        """Run the UPDATE command.
336 662
 
337
-    Returns:
338
-      escaped: 'string' with chars escaped appropriately
339
-    """
340
-    # test that the value is string, if not return it as is
341
-    try:
342
-      value.find('a')
343
-    except:
344
-      return value
345
-
346
-    escaped = value
347
-    escaped = escaped.replace('\\', '\\\\').replace('\0', '\\0')
348
-    escaped = escaped.replace(':', '\\:').replace('\n', '\\n')
349
-    escaped = escaped.replace('\r', '\\r').replace('\t', '\\t')
350
-    return escaped
351
-
352
-  def _UnEscapeChars(self, value):
353
-    """Un-escape necessary chars before returning to user.
354
-
355
-    Args:
356
-      value: 'string'
357
-
358
-    Returns:
359
-      escaped: 'string' with chars escaped appropriately
360
-    """
361
-    # test that the value is string, if not return it as is
362
-    try:
363
-      value.find('a')
364
-    except:
365
-      return value
366
-
367
-    escaped = value
368
-    escaped = escaped.replace('\\:', ':').replace('\\n', '\n')
369
-    escaped = escaped.replace('\\r', '\r').replace('\\t', '\t')
370
-    escaped = escaped.replace('\\0', '\0').replace('\\\\', '\\')
371
-    return escaped
372
-
373
-  def Execute(self, query, writethru=True):
374
-    """Parse and execute the query.
375
-
376
-    Args:
377
-      query: e.g. 'select * from table;'
378
-      writethru: bool
379
-
380
-    Returns:
381
-      dataset: [{col: val, col: val}, {col: val}, {col: val}]
382
-
383
-    Raises:
384
-      ExecuteError: unable to execute query
385
-    """
386
-    # parse the query
387
-    self.ParseQuery(query)
388
-
389
-    # get lock and execute the query
390
-    self.OpenTable()
391
-    Debug('Running ' + self.command)
392
-    dataset = []
393
-    if self.command == 'SELECT':
394
-      dataset = self._RunSelect()
395
-    elif self.command == 'UPDATE':
396
-      dataset = self._RunUpdate()
397
-    elif self.command == 'INSERT':
398
-      dataset = self._RunInsert()
399
-    elif self.command == 'DELETE':
400
-      dataset = self._RunDelete()
401
-
402
-    if self.command != 'SELECT' and writethru:
403
-      self.WriteTempTable()
404
-      self.MoveTableIntoPlace()
405
-
406
-    Debug(dataset)
407
-    return dataset
408
-
409
-  def CleanUp(self):
410
-    """Reset the internal variables (for multiple queries)."""
411
-    self.tokens = []          # query broken up into tokens
412
-    self.conditions = {}      # args to the WHERE clause
413
-    self.columns = []         # columns requested by SELECT
414
-    self.table = ''           # name of the table being queried
415
-    self.header = {}          # table header
416
-    self.orig_data = []       # original table data used to diff after updates
417
-    self.data = []            # table data as a list of dicts
418
-    self.count = False        # where or not using COUNT()
419
-    self.aliases = {}         # column aliases (SELECT AS)
420
-    self.targets = {}         # target columns-value pairs for INSERT/UPDATE
421
-    self.args = ''            # query arguments preceeding the ;
422
-    self.command = ''         # which command are we executing
423
-    self.strings = []         # list of string literals parsed from the query
424
-    self.parens = []          # list of parentheses parsed from the query
425
-
426
-  def ParseQuery(self, query):
427
-    """External wrapper for the query parsing routines.
428
-
429
-    Args:
430
-      query: string
431
-
432
-    Raises:
433
-      ParseError: Unable to parse query
434
-    """
435
-    self.args = query.split(';')[0]
436
-    self._Tokenize()
437
-    self._ParseCommand()
438
-    self._ParseOrderBy()
439
-    self._ParseConditions()
440
-    self._ParseColumns()
441
-    self._ParseTable()
442
-    self._ParseTargets()
443
-
444
-  def _ParseCommand(self):
445
-    """Determine the command: SELECT, UPDATE, DELETE or INSERT.
446
-
447
-    Raises:
448
-      ParseError: unable to parse command
449
-    """
450
-    self.command = self.tokens[0]
451
-    # Check that command is valid
452
-    if self.command not in self.ALL_COMMANDS:
453
-      raise ParseError('Unsupported command: ' + self.command)
454
-
455
-    self.tokens.pop(0)
456
-    Debug('Command is: %s' % self.command)
457
-    Debug('self.tokens: %s' % self.tokens)
458
-
459
-  def _Tokenize(self):
460
-    """Turn the string query into a list of tokens.
461
-
462
-    Split on '(', ')', ' ', ';', '=' and ','.
463
-    In addition capitalize any SQL keywords found.
464
-    """
465
-    # horrible hack to handle now()
466
-    time_now = '%s' % int(time.time())
467
-    time_now = time_now[0:-2] + '00'  # round off the seconds for unittesting
468
-    while 'now()' in self.args.lower():
469
-      start = self.args.lower().find('now()')
470
-      self.args = ('%s%s%s' % (self.args[0:start], time_now,
471
-                               self.args[start + 5:]))
472
-    # pad token separators with spaces
473
-    pad = self.args.replace('(', ' ( ').replace(')', ' ) ')
474
-    pad = pad.replace(',', ' , ').replace(';', ' ; ').replace('=', ' = ')
475
-    self.args = pad
476
-    # parse out all the blocks (string literals and parens)
477
-    self._ParseOutBlocks()
478
-    # split remaining into tokens
479
-    self.tokens = self.args.split()
480
-
481
-    # now capitalize
482
-    i = 0
483
-    while i < len(self.tokens):
484
-      if self.tokens[i].upper() in self.RESERVED_WORDS:
485
-        self.tokens[i] = self.tokens[i].upper()
486
-
487
-      i += 1
488
-
489
-    Debug('Tokens: %s' % self.tokens)
490
-
491
-  def _ParseOutBlocks(self):
492
-    """Parse out string literals and parenthesized values."""
493
-    self.strings = []
494
-    self.parens = []
495
-
496
-    # set str placeholder to a value that's not present in the string
497
-    while self._str_placeholder in self.args:
498
-      self._str_placeholder = '%s_' % self._str_placeholder
499
-
500
-    # set paren placeholder to a value that's not present in the string
501
-    while self._paren_placeholder in self.args:
502
-      self._paren_placeholder = '%s_' % self._paren_placeholder
503
-
504
-    self.strings = self._ParseOutHelper(self._str_placeholder, ["'", '"'],
505
-                                        'quotes')
506
-    self.parens = self._ParseOutHelper(self._paren_placeholder, ['(', ')'],
507
-                                       'parens')
508
-    Debug('Strings: %s' % self.strings)
509
-    Debug('Parens: %s' % self.parens)
510
-
511
-  def _ParseOutHelper(self, placeholder, delims, mode):
512
-    """Replace all text within delims with placeholders.
513
-
514
-    Args:
515
-      placeholder: string
516
-      delims: list of strings
517
-      mode: string
518
-          'parens': if there are 2 delims treat the first as opening
519
-                    and second as closing, such as with ( and )
520
-          'quotes': treat each delim as either opening or
521
-                    closing and require the same one to terminate the block,
522
-                    such as with ' and "
523
-
524
-    Returns:
525
-      list: [value1, value2, ...]
526
-
527
-    Raises:
528
-      ParseError: unable to parse out delims
529
-      ExecuteError: Invalid usage
530
-    """
531
-    if mode not in ['quotes', 'parens']:
532
-      raise ExecuteError('_ParseOutHelper: invalid mode ' + mode)
533
-    if mode == 'parens' and len(delims) != 2:
534
-      raise ExecuteError('_ParseOutHelper: delims must have 2 values '
535
-                         'in "parens" mode')
536
-    values = []
537
-    started = 0
538
-    new_args = ''
539
-    string = ''
540
-    my_id = 0
541
-    delim = ''
542
-    for c in self.args:
543
-      if c in delims:
544
-        if not started:
545
-          if mode == 'parens' and c != delims[0]:
546
-            raise ParseError('Found closing delimeter %s before '
547
-                             'corresponding %s' % (c, delims[0]))
548
-          started += 1
549
-          delim = c
550
-        else:
551
-          if ((mode == 'parens' and c == delim) or
552
-              (mode == 'quotes' and c != delim)):
553
-            string = '%s%s' % (string, c)
554
-            continue  # wait for matching delim
555
-
556
-          started -= 1
557
-          if not started:
558
-            values.append(string)
559
-            new_args = '%s %s' % (new_args, '%s%d' % (placeholder, my_id))
560
-            my_id += 1
561
-            string = ''
562
-
563
-      else:
564
-        if not started:
565
-          new_args = '%s%s' % (new_args, c)
566
-        else:
567
-          string = '%s%s' % (string, c)
568
-
569
-    if started:
570
-      if mode == 'parens':
571
-        waiting_for = delims[1]
572
-      else:
573
-        waiting_for = delim
574
-      raise ParseError('Unterminated block, waiting for ' + waiting_for)
575
-
576
-    self.args = new_args
577
-    Debug('Values: %s' % values)
578
-    return values
579
-
580
-  def _ReplaceStringLiterals(self, s, quotes=False):
581
-    """Replaces string placeholders with real values.
582
-
583
-    If quotes is set to True surround the returned value with single quotes
584
-
585
-    Args:
586
-      s: string
587
-      quotes: bool
588
-
589
-    Returns:
590
-      s: string
591
-    """
592
-    if s.strip().startswith(self._str_placeholder):
593
-      str_index = int(s.split(self._str_placeholder)[1])
594
-      s = self.strings[str_index]
595
-      if quotes:
596
-        s = "'" + s + "'"
597
-
598
-    return s
599
-
600
-  def _ReplaceParens(self, s):
601
-    """Replaces paren placeholders with real values.
602
-
603
-    Args:
604
-      s: string
605
-
606
-    Returns:
607
-      s: string
608
-    """
609
-    if s.strip().startswith(self._paren_placeholder):
610
-      str_index = int(s.split(self._paren_placeholder)[1])
611
-      s = self.parens[str_index].strip()
612
-
613
-    return s
614
-
615
-  def _RunDelete(self):
616
-    """Run the DELETE command.
617
-
618
-    Go through the rows in self.data matching them
619
-    against the conditions, if they fit delete the row leaving a placeholder
620
-    value (in order to keep the iteration process sane).  Afterward clean up
621
-    any empty values.
622
-
623
-    Returns:
624
-      dataset: [number of affected rows]
625
-    """
626
-    i = 0
627
-    length = len(self.data)
628
-    affected = 0
629
-    while i < length:
630
-      if self._MatchRow(self.data[i]):
631
-        self.data[i] = None
632
-        affected += 1
633
-
634
-      i += 1
635
-
636
-    # clean out the placeholders
637
-    while None in self.data:
638
-      self.data.remove(None)
639
-
640
-    return [affected]
641
-
642
-  def _RunUpdate(self):
643
-    """Run the UPDATE command.
644
-
645
-    Find the matching rows and update based on self.targets
646
-
647
-    Returns:
648
-      affected: [int]
649
-    Raises:
650
-      ExecuteError: failed to run UPDATE
651
-    """
652
-    i = 0
653
-    length = len(self.data)
654
-    affected = 0
655
-    while i < length:
656
-      if self._MatchRow(self.data[i]):
657
-        for target in self.targets:
658
-          if target not in self.header:
659
-            raise ExecuteError(target + ' is an invalid column name')
660
-          if self.header[target]['auto']:
661
-            raise ExecuteError(target + ' is type auto and connot be updated')
662
-
663
-          self.data[i][target] = self._TypeCheck(self.targets[target], target)
664
-        affected += 1
665
-
666
-      i += 1
667
-
668
-    return [affected]
669
-
670
-  def _RunInsert(self):
671
-    """Run the INSERT command.
672
-
673
-    Build up the row based on self.targets and table defaults, then append to
674
-    self.data
675
-
676
-    Returns:
677
-      affected: [int]
678
-    Raises:
679
-      ExecuteError: failed to run INSERT
680
-    """
681
-    new_row = {}
682
-    cols = self._SortHeaderColumns()
683
-    for col in cols:
684
-      if col in self.targets:
685
-        if self.header[col]['auto']:
686
-          raise ExecuteError(col + ' is type auto: cannot be modified')
687
-        new_row[col] = self.targets[col]
688
-
689
-      elif self.header[col]['null']:
690
-        new_row[col] = ''
691
-
692
-      elif self.header[col]['auto']:
693
-        new_row[col] = self._GetNextAuto(col)
694
-
695
-      else:
696
-        raise ExecuteError(col + ' cannot be empty or null')
697
-
698
-    self.data.append(new_row)
699
-    return [1]
700
-
701
-  def _GetNextAuto(self, col):
702
-    """Figure out the next value for col based on existing values.
663
+            Find the matching rows and update based on self.targets
703 664
 
704
-    Scan all the current values and return the highest one + 1.
665
+        Returns:
666
+            affected: [int]
667
+        Raises:
668
+            ExecuteError: failed to run UPDATE
669
+        """
670
+        i = 0
671
+        length = len(self.data)
672
+        affected = 0
673
+        while i < length:
674
+            if self._MatchRow(self.data[i]):
675
+                for target in self.targets:
676
+                    if target not in self.header:
677
+                        raise ExecuteError(target + ' is an invalid column ' +
678
+                                           'name')
679
+                    if self.header[target]['auto']:
680
+                        raise ExecuteError(target + ' is type auto and ' +
681
+                                           'cannot be updated')
682
+
683
+                    self.data[i][target] = \
684
+                        self._TypeCheck(self.targets[target], target)
685
+                affected += 1
686
+
687
+            i += 1
688
+
689
+        return [affected]
690
+
691
+    def _RunInsert(self):
692
+        """Run the INSERT command.
693
+
694
+            Build up the row based on self.targets and table defaults, then
695
+            append to self.data
696
+
697
+        Returns:
698
+            affected: [int]
699
+        Raises:
700
+            ExecuteError: failed to run INSERT
701
+        """
702
+        new_row = {}
703
+        cols = self._SortHeaderColumns()
704
+        for col in cols:
705
+            if col in self.targets:
706
+                if self.header[col]['auto']:
707
+                    raise ExecuteError(col + ' is type auto: cannot be ' +
708
+                                       'modified')
709
+                new_row[col] = self.targets[col]
710
+
711
+            elif self.header[col]['null']:
712
+                new_row[col] = ''
713
+
714
+            elif self.header[col]['auto']:
715
+                new_row[col] = self._GetNextAuto(col)
705 716
 
706
-    Args:
707
-      col: string
717
+        else:
718
+            raise ExecuteError(col + ' cannot be empty or null')
708 719
 
709
-    Returns:
710
-      next: int
720
+        self.data.append(new_row)
721
+        return [1]
711 722
 
712
-    Raises:
713
-      ExecuteError: Failed to get auto inc
714
-    """
715
-    highest = 0
716
-    seen = []
717
-    for row in self.data:
718
-      if row[col] > highest:
719
-        highest = row[col]
723
+    def _GetNextAuto(self, col):
724
+        """Figure out the next value for col based on existing values.