Skip to content

Commit 55208f6

Browse files
committed
Added a new SQL parser. This parser supports both SQL style line comments and comment blocks. SQL statements can span multiple lines but must be delimited with a semicolon. This allows for improved readability of SQL scripts.
The old parser expects one statement per line which may or may not end with a semicolon. This means the new parser can also process scripts written for the old parser if those scripts end each line with a semicolon. For backwards compatibility reasons this parser isn't used unless the manifest contains a meta-data entry "AA_SQL_PARSER" with the value "delimited". If this value isn't specified or is set to "legacy" the old parser implementation is used.
1 parent bd98740 commit 55208f6

File tree

5 files changed

+304
-21
lines changed

5 files changed

+304
-21
lines changed

src/com/activeandroid/Configuration.java

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,18 @@
2727
import com.activeandroid.util.ReflectionUtils;
2828

2929
public class Configuration {
30+
31+
public final static String SQL_PARSER_LEGACY = "legacy";
32+
public final static String SQL_PARSER_DELIMITED = "delimited";
33+
3034
//////////////////////////////////////////////////////////////////////////////////////
3135
// PRIVATE MEMBERS
3236
//////////////////////////////////////////////////////////////////////////////////////
3337

3438
private Context mContext;
3539
private String mDatabaseName;
3640
private int mDatabaseVersion;
41+
private String mSqlParser;
3742
private List<Class<? extends Model>> mModelClasses;
3843
private List<Class<? extends TypeSerializer>> mTypeSerializers;
3944
private int mCacheSize;
@@ -61,6 +66,10 @@ public String getDatabaseName() {
6166
public int getDatabaseVersion() {
6267
return mDatabaseVersion;
6368
}
69+
70+
public String getSqlParser() {
71+
return mSqlParser;
72+
}
6473

6574
public List<Class<? extends Model>> getModelClasses() {
6675
return mModelClasses;
@@ -91,9 +100,11 @@ public static class Builder {
91100
private static final String AA_DB_VERSION = "AA_DB_VERSION";
92101
private final static String AA_MODELS = "AA_MODELS";
93102
private final static String AA_SERIALIZERS = "AA_SERIALIZERS";
103+
private final static String AA_SQL_PARSER = "AA_SQL_PARSER";
94104

95105
private static final int DEFAULT_CACHE_SIZE = 1024;
96106
private static final String DEFAULT_DB_NAME = "Application.db";
107+
private static final String DEFAULT_SQL_PARSER = SQL_PARSER_LEGACY;
97108

98109
//////////////////////////////////////////////////////////////////////////////////////
99110
// PRIVATE MEMBERS
@@ -104,6 +115,7 @@ public static class Builder {
104115
private Integer mCacheSize;
105116
private String mDatabaseName;
106117
private Integer mDatabaseVersion;
118+
private String mSqlParser;
107119
private List<Class<? extends Model>> mModelClasses;
108120
private List<Class<? extends TypeSerializer>> mTypeSerializers;
109121

@@ -134,6 +146,11 @@ public Builder setDatabaseVersion(int databaseVersion) {
134146
mDatabaseVersion = databaseVersion;
135147
return this;
136148
}
149+
150+
public Builder setSqlParser(String sqlParser) {
151+
mSqlParser = sqlParser;
152+
return this;
153+
}
137154

138155
public Builder addModelClass(Class<? extends Model> modelClass) {
139156
if (mModelClasses == null) {
@@ -188,24 +205,28 @@ public Configuration create() {
188205
// Get database name from meta-data
189206
if (mDatabaseName != null) {
190207
configuration.mDatabaseName = mDatabaseName;
191-
}
192-
else {
208+
} else {
193209
configuration.mDatabaseName = getMetaDataDatabaseNameOrDefault();
194210
}
195211

196212
// Get database version from meta-data
197213
if (mDatabaseVersion != null) {
198214
configuration.mDatabaseVersion = mDatabaseVersion;
199-
}
200-
else {
215+
} else {
201216
configuration.mDatabaseVersion = getMetaDataDatabaseVersionOrDefault();
202217
}
203218

219+
// Get SQL parser from meta-data
220+
if (mSqlParser != null) {
221+
configuration.mSqlParser = mSqlParser;
222+
} else {
223+
configuration.mSqlParser = getMetaDataSqlParserOrDefault();
224+
}
225+
204226
// Get model classes from meta-data
205227
if (mModelClasses != null) {
206228
configuration.mModelClasses = mModelClasses;
207-
}
208-
else {
229+
} else {
209230
final String modelList = ReflectionUtils.getMetaData(mContext, AA_MODELS);
210231
if (modelList != null) {
211232
configuration.mModelClasses = loadModelList(modelList.split(","));
@@ -215,8 +236,7 @@ public Configuration create() {
215236
// Get type serializer classes from meta-data
216237
if (mTypeSerializers != null) {
217238
configuration.mTypeSerializers = mTypeSerializers;
218-
}
219-
else {
239+
} else {
220240
final String serializerList = ReflectionUtils.getMetaData(mContext, AA_SERIALIZERS);
221241
if (serializerList != null) {
222242
configuration.mTypeSerializers = loadSerializerList(serializerList.split(","));
@@ -250,6 +270,14 @@ private int getMetaDataDatabaseVersionOrDefault() {
250270
return aaVersion;
251271
}
252272

273+
private String getMetaDataSqlParserOrDefault() {
274+
final String mode = ReflectionUtils.getMetaData(mContext, AA_SQL_PARSER);
275+
if (mode == null) {
276+
return DEFAULT_SQL_PARSER;
277+
}
278+
return mode;
279+
}
280+
253281
private List<Class<? extends Model>> loadModelList(String[] models) {
254282
final List<Class<? extends Model>> modelClasses = new ArrayList<Class<? extends Model>>();
255283
final ClassLoader classLoader = mContext.getClass().getClassLoader();

src/com/activeandroid/DatabaseHelper.java

Lines changed: 61 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,11 @@
3131
import android.database.sqlite.SQLiteDatabase;
3232
import android.database.sqlite.SQLiteOpenHelper;
3333

34+
import com.activeandroid.util.IOUtils;
3435
import com.activeandroid.util.Log;
3536
import com.activeandroid.util.NaturalOrderComparator;
3637
import com.activeandroid.util.SQLiteUtils;
38+
import com.activeandroid.util.SqlParser;
3739

3840
public final class DatabaseHelper extends SQLiteOpenHelper {
3941
//////////////////////////////////////////////////////////////////////////////////////
@@ -42,13 +44,20 @@ public final class DatabaseHelper extends SQLiteOpenHelper {
4244

4345
public final static String MIGRATION_PATH = "migrations";
4446

47+
//////////////////////////////////////////////////////////////////////////////////////
48+
// PRIVATE FIELDS
49+
//////////////////////////////////////////////////////////////////////////////////////
50+
51+
private final String mSqlParser;
52+
4553
//////////////////////////////////////////////////////////////////////////////////////
4654
// CONSTRUCTORS
4755
//////////////////////////////////////////////////////////////////////////////////////
4856

4957
public DatabaseHelper(Configuration configuration) {
5058
super(configuration.getContext(), configuration.getDatabaseName(), null, configuration.getDatabaseVersion());
5159
copyAttachedDatabase(configuration.getContext(), configuration.getDatabaseName());
60+
mSqlParser = configuration.getSqlParser();
5261
}
5362

5463
//////////////////////////////////////////////////////////////////////////////////////
@@ -187,22 +196,61 @@ private boolean executeMigrations(SQLiteDatabase db, int oldVersion, int newVers
187196

188197
return migrationExecuted;
189198
}
190-
199+
191200
private void executeSqlScript(SQLiteDatabase db, String file) {
201+
202+
InputStream stream = null;
203+
192204
try {
193-
final InputStream input = Cache.getContext().getAssets().open(MIGRATION_PATH + "/" + file);
194-
final BufferedReader reader = new BufferedReader(new InputStreamReader(input));
195-
String line = null;
196-
197-
while ((line = reader.readLine()) != null) {
198-
line = line.replace(";", "").trim();
199-
if (!line.isEmpty()) {
200-
db.execSQL(line);
201-
}
202-
}
203-
}
204-
catch (IOException e) {
205+
stream = Cache.getContext().getAssets().open(MIGRATION_PATH + "/" + file);
206+
207+
if (Configuration.SQL_PARSER_DELIMITED.equalsIgnoreCase(mSqlParser)) {
208+
executeDelimitedSqlScript(db, stream);
209+
210+
} else {
211+
executeLegacySqlScript(db, stream);
212+
213+
}
214+
215+
} catch (IOException e) {
205216
Log.e("Failed to execute " + file, e);
217+
218+
} finally {
219+
IOUtils.closeQuietly(stream);
220+
206221
}
207222
}
223+
224+
private void executeDelimitedSqlScript(SQLiteDatabase db, InputStream stream) throws IOException {
225+
226+
List<String> commands = SqlParser.parse(stream);
227+
228+
for(String command : commands) {
229+
db.execSQL(command);
230+
}
231+
}
232+
233+
private void executeLegacySqlScript(SQLiteDatabase db, InputStream stream) throws IOException {
234+
235+
InputStreamReader reader = null;
236+
BufferedReader buffer = null;
237+
238+
try {
239+
reader = new InputStreamReader(stream);
240+
buffer = new BufferedReader(reader);
241+
String line = null;
242+
243+
while ((line = buffer.readLine()) != null) {
244+
line = line.replace(";", "").trim();
245+
if (!line.isEmpty()) {
246+
db.execSQL(line);
247+
}
248+
}
249+
250+
} finally {
251+
IOUtils.closeQuietly(buffer);
252+
IOUtils.closeQuietly(reader);
253+
254+
}
255+
}
208256
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
2+
package com.activeandroid.util;
3+
4+
import android.database.Cursor;
5+
6+
import java.io.Closeable;
7+
import java.io.IOException;
8+
9+
import com.activeandroid.util.Log;
10+
11+
12+
public class IOUtils {
13+
14+
/**
15+
* Unconditionally close a {@link Closeable}.
16+
* <p/>
17+
* Equivalent to {@link Closeable#close()}, except any exceptions will be ignored. This is
18+
* typically used in finally blocks.
19+
* @param closeable A {@link Closeable} to close.
20+
*/
21+
public static void closeQuietly(final Closeable closeable) {
22+
23+
if (closeable == null) {
24+
return;
25+
}
26+
27+
try {
28+
closeable.close();
29+
} catch (final IOException e) {
30+
Log.e("Couldn't close closeable.", e);
31+
}
32+
}
33+
34+
/**
35+
* Unconditionally close a {@link Cursor}.
36+
* <p/>
37+
* Equivalent to {@link Cursor#close()}, except any exceptions will be ignored. This is
38+
* typically used in finally blocks.
39+
* @param cursor A {@link Cursor} to close.
40+
*/
41+
public static void closeQuietly(final Cursor cursor) {
42+
43+
if (cursor == null) {
44+
return;
45+
}
46+
47+
try {
48+
cursor.close();
49+
} catch (final Exception e) {
50+
Log.e("Couldn't close cursor.", e);
51+
}
52+
}
53+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
2+
package com.activeandroid.util;
3+
4+
import java.io.BufferedInputStream;
5+
import java.io.IOException;
6+
import java.io.InputStream;
7+
import java.util.ArrayList;
8+
import java.util.List;
9+
10+
11+
public class SqlParser {
12+
13+
public final static int STATE_NONE = 0;
14+
public final static int STATE_STRING = 1;
15+
public final static int STATE_COMMENT = 2;
16+
public final static int STATE_COMMENT_BLOCK = 3;
17+
18+
public static List<String> parse(final InputStream stream) throws IOException {
19+
20+
final BufferedInputStream buffer = new BufferedInputStream(stream);
21+
final List<String> commands = new ArrayList<String>();
22+
final StringBuffer sb = new StringBuffer();
23+
24+
try {
25+
final Tokenizer tokenizer = new Tokenizer(buffer);
26+
int state = STATE_NONE;
27+
28+
while (tokenizer.hasNext()) {
29+
final char c = (char) tokenizer.next();
30+
31+
if (state == STATE_COMMENT_BLOCK) {
32+
if (tokenizer.skip("*/")) {
33+
state = STATE_NONE;
34+
}
35+
continue;
36+
37+
} else if (state == STATE_COMMENT) {
38+
if (isNewLine(c)) {
39+
state = STATE_NONE;
40+
}
41+
continue;
42+
43+
} else if (state == STATE_NONE && tokenizer.skip("/*")) {
44+
state = STATE_COMMENT_BLOCK;
45+
continue;
46+
47+
} else if (state == STATE_NONE && tokenizer.skip("--")) {
48+
state = STATE_COMMENT;
49+
continue;
50+
51+
} else if (state == STATE_NONE && c == ';') {
52+
final String command = sb.toString();
53+
commands.add(command);
54+
sb.setLength(0);
55+
continue;
56+
57+
} else if (state == STATE_NONE && c == '\'') {
58+
state = STATE_STRING;
59+
60+
} else if (state == STATE_STRING && c == '\'') {
61+
state = STATE_NONE;
62+
63+
}
64+
65+
if (state == STATE_NONE || state == STATE_STRING) {
66+
if (state == STATE_NONE && isWhitespace(c)) {
67+
if (sb.length() > 0 && sb.charAt(sb.length() - 1) != ' ') {
68+
sb.append(' ');
69+
}
70+
} else {
71+
sb.append(c);
72+
}
73+
}
74+
}
75+
76+
} finally {
77+
IOUtils.closeQuietly(buffer);
78+
}
79+
80+
if (sb.length() > 0) {
81+
commands.add(sb.toString());
82+
}
83+
84+
return commands;
85+
}
86+
87+
private static boolean isNewLine(final char c) {
88+
return c == '\r' || c == '\n';
89+
}
90+
91+
private static boolean isWhitespace(final char c) {
92+
return c == '\r' || c == '\n' || c == '\t' || c == ' ';
93+
}
94+
}

0 commit comments

Comments
 (0)