Repository: pandas-dev/pandas

Issue:
to_sql : MySQL identifier cannot be entirely numeric
Reported at StackOverflow: http://stackoverflow.com/questions/30586292/valueerror-mysql-identifier-cannot-be-entirely-numeric-with-pandas-to-sql

MySQL supports numeric identifiers (if quoted) (http://dev.mysql.com/doc/refman/5.0/en/identifiers.html) 
@artemyk Was there a reason we explicitely disallowed this? (https://github.com/pydata/pandas/blob/v0.16.1/pandas/io/sql.py#L1287)



Discussion:
@jorisvandenbossche Whoops!  I saw the following : "Identifiers may begin with a digit but unless quoted may not consist solely of digits" but did not pay attention to the "unless quoted".  Submitting a PR now.


Resolving patch:
diff --git a/pandas/io/sql.py b/pandas/io/sql.py
--- a/pandas/io/sql.py
+++ b/pandas/io/sql.py
@@ -1283,8 +1283,6 @@ def _get_valid_mysql_name(name):
         if not re.match(basere, c):
             if not (0x80 < ord(c) < 0xFFFF):
                 raise ValueError("Invalid MySQL identifier '%s'" % uname)
-    if not re.match(r'[^0-9]', uname):
-        raise ValueError('MySQL identifier cannot be entirely numeric')
 
     return '`' + uname + '`'
 


Test patch:
diff --git a/pandas/io/tests/test_sql.py b/pandas/io/tests/test_sql.py
--- a/pandas/io/tests/test_sql.py
+++ b/pandas/io/tests/test_sql.py
@@ -1738,7 +1738,8 @@ def test_illegal_names(self):
 
         for ndx, weird_name in enumerate(['test_weird_name]','test_weird_name[',
             'test_weird_name`','test_weird_name"', 'test_weird_name\'',
-            '_b.test_weird_name_01-30', '"_b.test_weird_name_01-30"']):
+            '_b.test_weird_name_01-30', '"_b.test_weird_name_01-30"',
+            '12345','12345blah']):
             df.to_sql(weird_name, self.conn, flavor=self.flavor)
             sql.table_exists(weird_name, self.conn)
 
@@ -1839,16 +1840,30 @@ def test_to_sql_save_index(self):
         self._to_sql_save_index()
 
     def test_illegal_names(self):
+        df = DataFrame([[1, 2], [3, 4]], columns=['a', 'b'])
+
+        # These tables and columns should be ok
+        for ndx, ok_name in enumerate(['99beginswithnumber','12345']):
+            df.to_sql(ok_name, self.conn, flavor=self.flavor, index=False,
+                      if_exists='replace')
+            self.conn.cursor().execute("DROP TABLE `%s`" % ok_name)
+            self.conn.commit()
+            df2 = DataFrame([[1, 2], [3, 4]], columns=['a', ok_name])
+            c_tbl = 'test_ok_col_name%d'%ndx
+            df2.to_sql(c_tbl, self.conn, flavor=self.flavor, index=False,
+                      if_exists='replace')   
+            self.conn.cursor().execute("DROP TABLE `%s`" % c_tbl)
+            self.conn.commit()
+
         # For MySQL, these should raise ValueError
         for ndx, illegal_name in enumerate(['test_illegal_name]','test_illegal_name[',
             'test_illegal_name`','test_illegal_name"', 'test_illegal_name\'', '']):
-            df = DataFrame([[1, 2], [3, 4]], columns=['a', 'b'])
             self.assertRaises(ValueError, df.to_sql, illegal_name, self.conn,
                 flavor=self.flavor, index=False)
 
             df2 = DataFrame([[1, 2], [3, 4]], columns=['a', illegal_name])
             c_tbl = 'test_illegal_col_name%d'%ndx
-            self.assertRaises(ValueError, df2.to_sql, 'test_illegal_col_name',
+            self.assertRaises(ValueError, df2.to_sql, c_tbl,
                 self.conn, flavor=self.flavor, index=False)