Loading core/java/android/database/DatabaseUtils.java +87 −37 Original line number Diff line number Diff line Loading @@ -77,6 +77,16 @@ public class DatabaseUtils { /** One of the values returned by {@link #getSqlStatementType(String)}. */ public static final int STATEMENT_OTHER = 99; // The following statement types are "extended" and are for internal use only. These types // are not public and are never returned by {@link #getSqlStatementType(String)}. /** An internal statement type @hide **/ public static final int STATEMENT_WITH = 100; /** An internal statement type @hide **/ public static final int STATEMENT_CREATE = 101; /** An internal statement type denoting a comment. @hide **/ public static final int STATEMENT_COMMENT = 102; /** * Special function for writing an exception result at the header of * a parcel, to be used when returning an exception from a transaction. Loading Loading @@ -1563,6 +1573,79 @@ public class DatabaseUtils { db.close(); } /** * The legacy prefix matcher. */ private static String getSqlStatementPrefixSimple(@NonNull String sql) { sql = sql.trim(); if (sql.length() < 3) { return null; } return sql.substring(0, 3).toUpperCase(Locale.ROOT); } /** * Return the extended statement type for the SQL statement. This is not a public API and it * can return values that are not publicly visible. * @hide */ private static int categorizeStatement(@NonNull String prefix, @NonNull String sql) { if (prefix == null) return STATEMENT_OTHER; switch (prefix) { case "SEL": return STATEMENT_SELECT; case "INS": case "UPD": case "REP": case "DEL": return STATEMENT_UPDATE; case "ATT": return STATEMENT_ATTACH; case "COM": case "END": return STATEMENT_COMMIT; case "ROL": if (sql.toUpperCase(Locale.ROOT).contains(" TO ")) { // Rollback to savepoint. return STATEMENT_OTHER; } return STATEMENT_ABORT; case "BEG": return STATEMENT_BEGIN; case "PRA": return STATEMENT_PRAGMA; case "CRE": return STATEMENT_CREATE; case "DRO": case "ALT": return STATEMENT_DDL; case "ANA": case "DET": return STATEMENT_UNPREPARED; case "WIT": return STATEMENT_WITH; default: if (prefix.startsWith("--") || prefix.startsWith("/*")) { return STATEMENT_COMMENT; } return STATEMENT_OTHER; } } /** * Return the extended statement type for the SQL statement. This is not a public API and it * can return values that are not publicly visible. * @hide */ public static int getSqlStatementTypeExtended(@NonNull String sql) { int type = categorizeStatement(getSqlStatementPrefixSimple(sql), sql); return type; } /** * Convert an extended statement type to a public SQL statement type value. * @hide */ public static int getSqlStatementType(int extended) { switch (extended) { case STATEMENT_CREATE: return STATEMENT_DDL; case STATEMENT_WITH: return STATEMENT_OTHER; case STATEMENT_COMMENT: return STATEMENT_OTHER; } return extended; } /** * Returns one of the following which represent the type of the given SQL statement. * <ol> Loading @@ -1572,49 +1655,16 @@ public class DatabaseUtils { * <li>{@link #STATEMENT_BEGIN}</li> * <li>{@link #STATEMENT_COMMIT}</li> * <li>{@link #STATEMENT_ABORT}</li> * <li>{@link #STATEMENT_PRAGMA}</li> * <li>{@link #STATEMENT_DDL}</li> * <li>{@link #STATEMENT_UNPREPARED}</li> * <li>{@link #STATEMENT_OTHER}</li> * </ol> * @param sql the SQL statement whose type is returned by this method * @return one of the values listed above */ public static int getSqlStatementType(String sql) { sql = sql.trim(); if (sql.length() < 3) { return STATEMENT_OTHER; } String prefixSql = sql.substring(0, 3).toUpperCase(Locale.ROOT); if (prefixSql.equals("SEL")) { return STATEMENT_SELECT; } else if (prefixSql.equals("INS") || prefixSql.equals("UPD") || prefixSql.equals("REP") || prefixSql.equals("DEL")) { return STATEMENT_UPDATE; } else if (prefixSql.equals("ATT")) { return STATEMENT_ATTACH; } else if (prefixSql.equals("COM")) { return STATEMENT_COMMIT; } else if (prefixSql.equals("END")) { return STATEMENT_COMMIT; } else if (prefixSql.equals("ROL")) { boolean isRollbackToSavepoint = sql.toUpperCase(Locale.ROOT).contains(" TO "); if (isRollbackToSavepoint) { Log.w(TAG, "Statement '" + sql + "' may not work on API levels 16-27, use ';" + sql + "' instead"); return STATEMENT_OTHER; } return STATEMENT_ABORT; } else if (prefixSql.equals("BEG")) { return STATEMENT_BEGIN; } else if (prefixSql.equals("PRA")) { return STATEMENT_PRAGMA; } else if (prefixSql.equals("CRE") || prefixSql.equals("DRO") || prefixSql.equals("ALT")) { return STATEMENT_DDL; } else if (prefixSql.equals("ANA") || prefixSql.equals("DET")) { return STATEMENT_UNPREPARED; } return STATEMENT_OTHER; return getSqlStatementType(getSqlStatementTypeExtended(sql)); } /** Loading core/java/android/database/sqlite/SQLiteConnection.java +3 −2 Original line number Diff line number Diff line Loading @@ -1096,7 +1096,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen seqNum = mPreparedStatementCache.getLastSeqNum(); try { final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr); final int type = DatabaseUtils.getSqlStatementType(sql); final int type = DatabaseUtils.getSqlStatementTypeExtended(sql); final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr); statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly, seqNum); Loading Loading @@ -1279,7 +1279,8 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen private static boolean isCacheable(int statementType) { if (statementType == DatabaseUtils.STATEMENT_UPDATE || statementType == DatabaseUtils.STATEMENT_SELECT) { || statementType == DatabaseUtils.STATEMENT_SELECT || statementType == DatabaseUtils.STATEMENT_WITH) { return true; } return false; Loading core/tests/coretests/src/android/database/DatabaseUtilsTest.java +44 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,15 @@ package android.database; import static android.database.DatabaseUtils.bindSelection; import static android.database.DatabaseUtils.getSqlStatementType; import static android.database.DatabaseUtils.getSqlStatementTypeExtended; import static android.database.DatabaseUtils.STATEMENT_COMMENT; import static android.database.DatabaseUtils.STATEMENT_CREATE; import static android.database.DatabaseUtils.STATEMENT_DDL; import static android.database.DatabaseUtils.STATEMENT_OTHER; import static android.database.DatabaseUtils.STATEMENT_SELECT; import static android.database.DatabaseUtils.STATEMENT_UPDATE; import static android.database.DatabaseUtils.STATEMENT_WITH; import static org.junit.Assert.assertEquals; Loading Loading @@ -63,4 +72,39 @@ public class DatabaseUtilsTest { bindSelection("foo=?10 AND bar=? AND meow=?1", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)); } @Test public void testStatementType() { final int sel = STATEMENT_SELECT; assertEquals(sel, getSqlStatementType("SELECT")); assertEquals(sel, getSqlStatementType(" SELECT")); assertEquals(sel, getSqlStatementType(" \n SELECT")); final int upd = STATEMENT_UPDATE; assertEquals(upd, getSqlStatementType("UPDATE")); assertEquals(upd, getSqlStatementType(" UPDATE")); assertEquals(upd, getSqlStatementType(" \n UPDATE")); final int ddl = STATEMENT_DDL; assertEquals(ddl, getSqlStatementType("ALTER TABLE t1 ADD COLUMN j int")); assertEquals(ddl, getSqlStatementType("CREATE TABLE t1 (i int)")); // Short statements, leading comments, and WITH are decoded to "other" in the public API. final int othr = STATEMENT_OTHER; assertEquals(othr, getSqlStatementType("SE")); assertEquals(othr, getSqlStatementType("SE LECT")); assertEquals(othr, getSqlStatementType("-- cmt\n SE")); assertEquals(othr, getSqlStatementType("WITH")); // Test the extended statement types. final int wit = STATEMENT_WITH; assertEquals(wit, getSqlStatementTypeExtended("WITH")); final int cmt = STATEMENT_COMMENT; assertEquals(cmt, getSqlStatementTypeExtended("-- cmt\n SELECT")); final int cre = STATEMENT_CREATE; assertEquals(cre, getSqlStatementTypeExtended("CREATE TABLE t1 (i int)")); } } Loading
core/java/android/database/DatabaseUtils.java +87 −37 Original line number Diff line number Diff line Loading @@ -77,6 +77,16 @@ public class DatabaseUtils { /** One of the values returned by {@link #getSqlStatementType(String)}. */ public static final int STATEMENT_OTHER = 99; // The following statement types are "extended" and are for internal use only. These types // are not public and are never returned by {@link #getSqlStatementType(String)}. /** An internal statement type @hide **/ public static final int STATEMENT_WITH = 100; /** An internal statement type @hide **/ public static final int STATEMENT_CREATE = 101; /** An internal statement type denoting a comment. @hide **/ public static final int STATEMENT_COMMENT = 102; /** * Special function for writing an exception result at the header of * a parcel, to be used when returning an exception from a transaction. Loading Loading @@ -1563,6 +1573,79 @@ public class DatabaseUtils { db.close(); } /** * The legacy prefix matcher. */ private static String getSqlStatementPrefixSimple(@NonNull String sql) { sql = sql.trim(); if (sql.length() < 3) { return null; } return sql.substring(0, 3).toUpperCase(Locale.ROOT); } /** * Return the extended statement type for the SQL statement. This is not a public API and it * can return values that are not publicly visible. * @hide */ private static int categorizeStatement(@NonNull String prefix, @NonNull String sql) { if (prefix == null) return STATEMENT_OTHER; switch (prefix) { case "SEL": return STATEMENT_SELECT; case "INS": case "UPD": case "REP": case "DEL": return STATEMENT_UPDATE; case "ATT": return STATEMENT_ATTACH; case "COM": case "END": return STATEMENT_COMMIT; case "ROL": if (sql.toUpperCase(Locale.ROOT).contains(" TO ")) { // Rollback to savepoint. return STATEMENT_OTHER; } return STATEMENT_ABORT; case "BEG": return STATEMENT_BEGIN; case "PRA": return STATEMENT_PRAGMA; case "CRE": return STATEMENT_CREATE; case "DRO": case "ALT": return STATEMENT_DDL; case "ANA": case "DET": return STATEMENT_UNPREPARED; case "WIT": return STATEMENT_WITH; default: if (prefix.startsWith("--") || prefix.startsWith("/*")) { return STATEMENT_COMMENT; } return STATEMENT_OTHER; } } /** * Return the extended statement type for the SQL statement. This is not a public API and it * can return values that are not publicly visible. * @hide */ public static int getSqlStatementTypeExtended(@NonNull String sql) { int type = categorizeStatement(getSqlStatementPrefixSimple(sql), sql); return type; } /** * Convert an extended statement type to a public SQL statement type value. * @hide */ public static int getSqlStatementType(int extended) { switch (extended) { case STATEMENT_CREATE: return STATEMENT_DDL; case STATEMENT_WITH: return STATEMENT_OTHER; case STATEMENT_COMMENT: return STATEMENT_OTHER; } return extended; } /** * Returns one of the following which represent the type of the given SQL statement. * <ol> Loading @@ -1572,49 +1655,16 @@ public class DatabaseUtils { * <li>{@link #STATEMENT_BEGIN}</li> * <li>{@link #STATEMENT_COMMIT}</li> * <li>{@link #STATEMENT_ABORT}</li> * <li>{@link #STATEMENT_PRAGMA}</li> * <li>{@link #STATEMENT_DDL}</li> * <li>{@link #STATEMENT_UNPREPARED}</li> * <li>{@link #STATEMENT_OTHER}</li> * </ol> * @param sql the SQL statement whose type is returned by this method * @return one of the values listed above */ public static int getSqlStatementType(String sql) { sql = sql.trim(); if (sql.length() < 3) { return STATEMENT_OTHER; } String prefixSql = sql.substring(0, 3).toUpperCase(Locale.ROOT); if (prefixSql.equals("SEL")) { return STATEMENT_SELECT; } else if (prefixSql.equals("INS") || prefixSql.equals("UPD") || prefixSql.equals("REP") || prefixSql.equals("DEL")) { return STATEMENT_UPDATE; } else if (prefixSql.equals("ATT")) { return STATEMENT_ATTACH; } else if (prefixSql.equals("COM")) { return STATEMENT_COMMIT; } else if (prefixSql.equals("END")) { return STATEMENT_COMMIT; } else if (prefixSql.equals("ROL")) { boolean isRollbackToSavepoint = sql.toUpperCase(Locale.ROOT).contains(" TO "); if (isRollbackToSavepoint) { Log.w(TAG, "Statement '" + sql + "' may not work on API levels 16-27, use ';" + sql + "' instead"); return STATEMENT_OTHER; } return STATEMENT_ABORT; } else if (prefixSql.equals("BEG")) { return STATEMENT_BEGIN; } else if (prefixSql.equals("PRA")) { return STATEMENT_PRAGMA; } else if (prefixSql.equals("CRE") || prefixSql.equals("DRO") || prefixSql.equals("ALT")) { return STATEMENT_DDL; } else if (prefixSql.equals("ANA") || prefixSql.equals("DET")) { return STATEMENT_UNPREPARED; } return STATEMENT_OTHER; return getSqlStatementType(getSqlStatementTypeExtended(sql)); } /** Loading
core/java/android/database/sqlite/SQLiteConnection.java +3 −2 Original line number Diff line number Diff line Loading @@ -1096,7 +1096,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen seqNum = mPreparedStatementCache.getLastSeqNum(); try { final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr); final int type = DatabaseUtils.getSqlStatementType(sql); final int type = DatabaseUtils.getSqlStatementTypeExtended(sql); final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr); statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly, seqNum); Loading Loading @@ -1279,7 +1279,8 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen private static boolean isCacheable(int statementType) { if (statementType == DatabaseUtils.STATEMENT_UPDATE || statementType == DatabaseUtils.STATEMENT_SELECT) { || statementType == DatabaseUtils.STATEMENT_SELECT || statementType == DatabaseUtils.STATEMENT_WITH) { return true; } return false; Loading
core/tests/coretests/src/android/database/DatabaseUtilsTest.java +44 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,15 @@ package android.database; import static android.database.DatabaseUtils.bindSelection; import static android.database.DatabaseUtils.getSqlStatementType; import static android.database.DatabaseUtils.getSqlStatementTypeExtended; import static android.database.DatabaseUtils.STATEMENT_COMMENT; import static android.database.DatabaseUtils.STATEMENT_CREATE; import static android.database.DatabaseUtils.STATEMENT_DDL; import static android.database.DatabaseUtils.STATEMENT_OTHER; import static android.database.DatabaseUtils.STATEMENT_SELECT; import static android.database.DatabaseUtils.STATEMENT_UPDATE; import static android.database.DatabaseUtils.STATEMENT_WITH; import static org.junit.Assert.assertEquals; Loading Loading @@ -63,4 +72,39 @@ public class DatabaseUtilsTest { bindSelection("foo=?10 AND bar=? AND meow=?1", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)); } @Test public void testStatementType() { final int sel = STATEMENT_SELECT; assertEquals(sel, getSqlStatementType("SELECT")); assertEquals(sel, getSqlStatementType(" SELECT")); assertEquals(sel, getSqlStatementType(" \n SELECT")); final int upd = STATEMENT_UPDATE; assertEquals(upd, getSqlStatementType("UPDATE")); assertEquals(upd, getSqlStatementType(" UPDATE")); assertEquals(upd, getSqlStatementType(" \n UPDATE")); final int ddl = STATEMENT_DDL; assertEquals(ddl, getSqlStatementType("ALTER TABLE t1 ADD COLUMN j int")); assertEquals(ddl, getSqlStatementType("CREATE TABLE t1 (i int)")); // Short statements, leading comments, and WITH are decoded to "other" in the public API. final int othr = STATEMENT_OTHER; assertEquals(othr, getSqlStatementType("SE")); assertEquals(othr, getSqlStatementType("SE LECT")); assertEquals(othr, getSqlStatementType("-- cmt\n SE")); assertEquals(othr, getSqlStatementType("WITH")); // Test the extended statement types. final int wit = STATEMENT_WITH; assertEquals(wit, getSqlStatementTypeExtended("WITH")); final int cmt = STATEMENT_COMMENT; assertEquals(cmt, getSqlStatementTypeExtended("-- cmt\n SELECT")); final int cre = STATEMENT_CREATE; assertEquals(cre, getSqlStatementTypeExtended("CREATE TABLE t1 (i int)")); } }