1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.database;
18
19import org.apache.commons.codec.binary.Hex;
20
21import android.content.ContentValues;
22import android.content.Context;
23import android.content.OperationApplicationException;
24import android.database.sqlite.SQLiteAbortException;
25import android.database.sqlite.SQLiteConstraintException;
26import android.database.sqlite.SQLiteDatabase;
27import android.database.sqlite.SQLiteDatabaseCorruptException;
28import android.database.sqlite.SQLiteDiskIOException;
29import android.database.sqlite.SQLiteException;
30import android.database.sqlite.SQLiteFullException;
31import android.database.sqlite.SQLiteProgram;
32import android.database.sqlite.SQLiteStatement;
33import android.os.OperationCanceledException;
34import android.os.Parcel;
35import android.os.ParcelFileDescriptor;
36import android.text.TextUtils;
37import android.util.Log;
38
39import java.io.FileNotFoundException;
40import java.io.PrintStream;
41import java.text.Collator;
42import java.util.HashMap;
43import java.util.Locale;
44import java.util.Map;
45
46/**
47 * Static utility methods for dealing with databases and {@link Cursor}s.
48 */
49public class DatabaseUtils {
50    private static final String TAG = "DatabaseUtils";
51
52    private static final boolean DEBUG = false;
53
54    /** One of the values returned by {@link #getSqlStatementType(String)}. */
55    public static final int STATEMENT_SELECT = 1;
56    /** One of the values returned by {@link #getSqlStatementType(String)}. */
57    public static final int STATEMENT_UPDATE = 2;
58    /** One of the values returned by {@link #getSqlStatementType(String)}. */
59    public static final int STATEMENT_ATTACH = 3;
60    /** One of the values returned by {@link #getSqlStatementType(String)}. */
61    public static final int STATEMENT_BEGIN = 4;
62    /** One of the values returned by {@link #getSqlStatementType(String)}. */
63    public static final int STATEMENT_COMMIT = 5;
64    /** One of the values returned by {@link #getSqlStatementType(String)}. */
65    public static final int STATEMENT_ABORT = 6;
66    /** One of the values returned by {@link #getSqlStatementType(String)}. */
67    public static final int STATEMENT_PRAGMA = 7;
68    /** One of the values returned by {@link #getSqlStatementType(String)}. */
69    public static final int STATEMENT_DDL = 8;
70    /** One of the values returned by {@link #getSqlStatementType(String)}. */
71    public static final int STATEMENT_UNPREPARED = 9;
72    /** One of the values returned by {@link #getSqlStatementType(String)}. */
73    public static final int STATEMENT_OTHER = 99;
74
75    /**
76     * Special function for writing an exception result at the header of
77     * a parcel, to be used when returning an exception from a transaction.
78     * exception will be re-thrown by the function in another process
79     * @param reply Parcel to write to
80     * @param e The Exception to be written.
81     * @see Parcel#writeNoException
82     * @see Parcel#writeException
83     */
84    public static final void writeExceptionToParcel(Parcel reply, Exception e) {
85        int code = 0;
86        boolean logException = true;
87        if (e instanceof FileNotFoundException) {
88            code = 1;
89            logException = false;
90        } else if (e instanceof IllegalArgumentException) {
91            code = 2;
92        } else if (e instanceof UnsupportedOperationException) {
93            code = 3;
94        } else if (e instanceof SQLiteAbortException) {
95            code = 4;
96        } else if (e instanceof SQLiteConstraintException) {
97            code = 5;
98        } else if (e instanceof SQLiteDatabaseCorruptException) {
99            code = 6;
100        } else if (e instanceof SQLiteFullException) {
101            code = 7;
102        } else if (e instanceof SQLiteDiskIOException) {
103            code = 8;
104        } else if (e instanceof SQLiteException) {
105            code = 9;
106        } else if (e instanceof OperationApplicationException) {
107            code = 10;
108        } else if (e instanceof OperationCanceledException) {
109            code = 11;
110            logException = false;
111        } else {
112            reply.writeException(e);
113            Log.e(TAG, "Writing exception to parcel", e);
114            return;
115        }
116        reply.writeInt(code);
117        reply.writeString(e.getMessage());
118
119        if (logException) {
120            Log.e(TAG, "Writing exception to parcel", e);
121        }
122    }
123
124    /**
125     * Special function for reading an exception result from the header of
126     * a parcel, to be used after receiving the result of a transaction.  This
127     * will throw the exception for you if it had been written to the Parcel,
128     * otherwise return and let you read the normal result data from the Parcel.
129     * @param reply Parcel to read from
130     * @see Parcel#writeNoException
131     * @see Parcel#readException
132     */
133    public static final void readExceptionFromParcel(Parcel reply) {
134        int code = reply.readExceptionCode();
135        if (code == 0) return;
136        String msg = reply.readString();
137        DatabaseUtils.readExceptionFromParcel(reply, msg, code);
138    }
139
140    public static void readExceptionWithFileNotFoundExceptionFromParcel(
141            Parcel reply) throws FileNotFoundException {
142        int code = reply.readExceptionCode();
143        if (code == 0) return;
144        String msg = reply.readString();
145        if (code == 1) {
146            throw new FileNotFoundException(msg);
147        } else {
148            DatabaseUtils.readExceptionFromParcel(reply, msg, code);
149        }
150    }
151
152    public static void readExceptionWithOperationApplicationExceptionFromParcel(
153            Parcel reply) throws OperationApplicationException {
154        int code = reply.readExceptionCode();
155        if (code == 0) return;
156        String msg = reply.readString();
157        if (code == 10) {
158            throw new OperationApplicationException(msg);
159        } else {
160            DatabaseUtils.readExceptionFromParcel(reply, msg, code);
161        }
162    }
163
164    private static final void readExceptionFromParcel(Parcel reply, String msg, int code) {
165        switch (code) {
166            case 2:
167                throw new IllegalArgumentException(msg);
168            case 3:
169                throw new UnsupportedOperationException(msg);
170            case 4:
171                throw new SQLiteAbortException(msg);
172            case 5:
173                throw new SQLiteConstraintException(msg);
174            case 6:
175                throw new SQLiteDatabaseCorruptException(msg);
176            case 7:
177                throw new SQLiteFullException(msg);
178            case 8:
179                throw new SQLiteDiskIOException(msg);
180            case 9:
181                throw new SQLiteException(msg);
182            case 11:
183                throw new OperationCanceledException(msg);
184            default:
185                reply.readException(code, msg);
186        }
187    }
188
189    /**
190     * Binds the given Object to the given SQLiteProgram using the proper
191     * typing. For example, bind numbers as longs/doubles, and everything else
192     * as a string by call toString() on it.
193     *
194     * @param prog the program to bind the object to
195     * @param index the 1-based index to bind at
196     * @param value the value to bind
197     */
198    public static void bindObjectToProgram(SQLiteProgram prog, int index,
199            Object value) {
200        if (value == null) {
201            prog.bindNull(index);
202        } else if (value instanceof Double || value instanceof Float) {
203            prog.bindDouble(index, ((Number)value).doubleValue());
204        } else if (value instanceof Number) {
205            prog.bindLong(index, ((Number)value).longValue());
206        } else if (value instanceof Boolean) {
207            Boolean bool = (Boolean)value;
208            if (bool) {
209                prog.bindLong(index, 1);
210            } else {
211                prog.bindLong(index, 0);
212            }
213        } else if (value instanceof byte[]){
214            prog.bindBlob(index, (byte[]) value);
215        } else {
216            prog.bindString(index, value.toString());
217        }
218    }
219
220    /**
221     * Returns data type of the given object's value.
222     *<p>
223     * Returned values are
224     * <ul>
225     *   <li>{@link Cursor#FIELD_TYPE_NULL}</li>
226     *   <li>{@link Cursor#FIELD_TYPE_INTEGER}</li>
227     *   <li>{@link Cursor#FIELD_TYPE_FLOAT}</li>
228     *   <li>{@link Cursor#FIELD_TYPE_STRING}</li>
229     *   <li>{@link Cursor#FIELD_TYPE_BLOB}</li>
230     *</ul>
231     *</p>
232     *
233     * @param obj the object whose value type is to be returned
234     * @return object value type
235     * @hide
236     */
237    public static int getTypeOfObject(Object obj) {
238        if (obj == null) {
239            return Cursor.FIELD_TYPE_NULL;
240        } else if (obj instanceof byte[]) {
241            return Cursor.FIELD_TYPE_BLOB;
242        } else if (obj instanceof Float || obj instanceof Double) {
243            return Cursor.FIELD_TYPE_FLOAT;
244        } else if (obj instanceof Long || obj instanceof Integer
245                || obj instanceof Short || obj instanceof Byte) {
246            return Cursor.FIELD_TYPE_INTEGER;
247        } else {
248            return Cursor.FIELD_TYPE_STRING;
249        }
250    }
251
252    /**
253     * Fills the specified cursor window by iterating over the contents of the cursor.
254     * The window is filled until the cursor is exhausted or the window runs out
255     * of space.
256     *
257     * The original position of the cursor is left unchanged by this operation.
258     *
259     * @param cursor The cursor that contains the data to put in the window.
260     * @param position The start position for filling the window.
261     * @param window The window to fill.
262     * @hide
263     */
264    public static void cursorFillWindow(final Cursor cursor,
265            int position, final CursorWindow window) {
266        if (position < 0 || position >= cursor.getCount()) {
267            return;
268        }
269        final int oldPos = cursor.getPosition();
270        final int numColumns = cursor.getColumnCount();
271        window.clear();
272        window.setStartPosition(position);
273        window.setNumColumns(numColumns);
274        if (cursor.moveToPosition(position)) {
275            do {
276                if (!window.allocRow()) {
277                    break;
278                }
279                for (int i = 0; i < numColumns; i++) {
280                    final int type = cursor.getType(i);
281                    final boolean success;
282                    switch (type) {
283                        case Cursor.FIELD_TYPE_NULL:
284                            success = window.putNull(position, i);
285                            break;
286
287                        case Cursor.FIELD_TYPE_INTEGER:
288                            success = window.putLong(cursor.getLong(i), position, i);
289                            break;
290
291                        case Cursor.FIELD_TYPE_FLOAT:
292                            success = window.putDouble(cursor.getDouble(i), position, i);
293                            break;
294
295                        case Cursor.FIELD_TYPE_BLOB: {
296                            final byte[] value = cursor.getBlob(i);
297                            success = value != null ? window.putBlob(value, position, i)
298                                    : window.putNull(position, i);
299                            break;
300                        }
301
302                        default: // assume value is convertible to String
303                        case Cursor.FIELD_TYPE_STRING: {
304                            final String value = cursor.getString(i);
305                            success = value != null ? window.putString(value, position, i)
306                                    : window.putNull(position, i);
307                            break;
308                        }
309                    }
310                    if (!success) {
311                        window.freeLastRow();
312                        break;
313                    }
314                }
315                position += 1;
316            } while (cursor.moveToNext());
317        }
318        cursor.moveToPosition(oldPos);
319    }
320
321    /**
322     * Appends an SQL string to the given StringBuilder, including the opening
323     * and closing single quotes. Any single quotes internal to sqlString will
324     * be escaped.
325     *
326     * This method is deprecated because we want to encourage everyone
327     * to use the "?" binding form.  However, when implementing a
328     * ContentProvider, one may want to add WHERE clauses that were
329     * not provided by the caller.  Since "?" is a positional form,
330     * using it in this case could break the caller because the
331     * indexes would be shifted to accomodate the ContentProvider's
332     * internal bindings.  In that case, it may be necessary to
333     * construct a WHERE clause manually.  This method is useful for
334     * those cases.
335     *
336     * @param sb the StringBuilder that the SQL string will be appended to
337     * @param sqlString the raw string to be appended, which may contain single
338     *                  quotes
339     */
340    public static void appendEscapedSQLString(StringBuilder sb, String sqlString) {
341        sb.append('\'');
342        if (sqlString.indexOf('\'') != -1) {
343            int length = sqlString.length();
344            for (int i = 0; i < length; i++) {
345                char c = sqlString.charAt(i);
346                if (c == '\'') {
347                    sb.append('\'');
348                }
349                sb.append(c);
350            }
351        } else
352            sb.append(sqlString);
353        sb.append('\'');
354    }
355
356    /**
357     * SQL-escape a string.
358     */
359    public static String sqlEscapeString(String value) {
360        StringBuilder escaper = new StringBuilder();
361
362        DatabaseUtils.appendEscapedSQLString(escaper, value);
363
364        return escaper.toString();
365    }
366
367    /**
368     * Appends an Object to an SQL string with the proper escaping, etc.
369     */
370    public static final void appendValueToSql(StringBuilder sql, Object value) {
371        if (value == null) {
372            sql.append("NULL");
373        } else if (value instanceof Boolean) {
374            Boolean bool = (Boolean)value;
375            if (bool) {
376                sql.append('1');
377            } else {
378                sql.append('0');
379            }
380        } else {
381            appendEscapedSQLString(sql, value.toString());
382        }
383    }
384
385    /**
386     * Concatenates two SQL WHERE clauses, handling empty or null values.
387     */
388    public static String concatenateWhere(String a, String b) {
389        if (TextUtils.isEmpty(a)) {
390            return b;
391        }
392        if (TextUtils.isEmpty(b)) {
393            return a;
394        }
395
396        return "(" + a + ") AND (" + b + ")";
397    }
398
399    /**
400     * return the collation key
401     * @param name
402     * @return the collation key
403     */
404    public static String getCollationKey(String name) {
405        byte [] arr = getCollationKeyInBytes(name);
406        try {
407            return new String(arr, 0, getKeyLen(arr), "ISO8859_1");
408        } catch (Exception ex) {
409            return "";
410        }
411    }
412
413    /**
414     * return the collation key in hex format
415     * @param name
416     * @return the collation key in hex format
417     */
418    public static String getHexCollationKey(String name) {
419        byte [] arr = getCollationKeyInBytes(name);
420        char[] keys = Hex.encodeHex(arr);
421        return new String(keys, 0, getKeyLen(arr) * 2);
422    }
423
424    private static int getKeyLen(byte[] arr) {
425        if (arr[arr.length - 1] != 0) {
426            return arr.length;
427        } else {
428            // remove zero "termination"
429            return arr.length-1;
430        }
431    }
432
433    private static byte[] getCollationKeyInBytes(String name) {
434        if (mColl == null) {
435            mColl = Collator.getInstance();
436            mColl.setStrength(Collator.PRIMARY);
437        }
438        return mColl.getCollationKey(name).toByteArray();
439    }
440
441    private static Collator mColl = null;
442    /**
443     * Prints the contents of a Cursor to System.out. The position is restored
444     * after printing.
445     *
446     * @param cursor the cursor to print
447     */
448    public static void dumpCursor(Cursor cursor) {
449        dumpCursor(cursor, System.out);
450    }
451
452    /**
453     * Prints the contents of a Cursor to a PrintSteam. The position is restored
454     * after printing.
455     *
456     * @param cursor the cursor to print
457     * @param stream the stream to print to
458     */
459    public static void dumpCursor(Cursor cursor, PrintStream stream) {
460        stream.println(">>>>> Dumping cursor " + cursor);
461        if (cursor != null) {
462            int startPos = cursor.getPosition();
463
464            cursor.moveToPosition(-1);
465            while (cursor.moveToNext()) {
466                dumpCurrentRow(cursor, stream);
467            }
468            cursor.moveToPosition(startPos);
469        }
470        stream.println("<<<<<");
471    }
472
473    /**
474     * Prints the contents of a Cursor to a StringBuilder. The position
475     * is restored after printing.
476     *
477     * @param cursor the cursor to print
478     * @param sb the StringBuilder to print to
479     */
480    public static void dumpCursor(Cursor cursor, StringBuilder sb) {
481        sb.append(">>>>> Dumping cursor " + cursor + "\n");
482        if (cursor != null) {
483            int startPos = cursor.getPosition();
484
485            cursor.moveToPosition(-1);
486            while (cursor.moveToNext()) {
487                dumpCurrentRow(cursor, sb);
488            }
489            cursor.moveToPosition(startPos);
490        }
491        sb.append("<<<<<\n");
492    }
493
494    /**
495     * Prints the contents of a Cursor to a String. The position is restored
496     * after printing.
497     *
498     * @param cursor the cursor to print
499     * @return a String that contains the dumped cursor
500     */
501    public static String dumpCursorToString(Cursor cursor) {
502        StringBuilder sb = new StringBuilder();
503        dumpCursor(cursor, sb);
504        return sb.toString();
505    }
506
507    /**
508     * Prints the contents of a Cursor's current row to System.out.
509     *
510     * @param cursor the cursor to print from
511     */
512    public static void dumpCurrentRow(Cursor cursor) {
513        dumpCurrentRow(cursor, System.out);
514    }
515
516    /**
517     * Prints the contents of a Cursor's current row to a PrintSteam.
518     *
519     * @param cursor the cursor to print
520     * @param stream the stream to print to
521     */
522    public static void dumpCurrentRow(Cursor cursor, PrintStream stream) {
523        String[] cols = cursor.getColumnNames();
524        stream.println("" + cursor.getPosition() + " {");
525        int length = cols.length;
526        for (int i = 0; i< length; i++) {
527            String value;
528            try {
529                value = cursor.getString(i);
530            } catch (SQLiteException e) {
531                // assume that if the getString threw this exception then the column is not
532                // representable by a string, e.g. it is a BLOB.
533                value = "<unprintable>";
534            }
535            stream.println("   " + cols[i] + '=' + value);
536        }
537        stream.println("}");
538    }
539
540    /**
541     * Prints the contents of a Cursor's current row to a StringBuilder.
542     *
543     * @param cursor the cursor to print
544     * @param sb the StringBuilder to print to
545     */
546    public static void dumpCurrentRow(Cursor cursor, StringBuilder sb) {
547        String[] cols = cursor.getColumnNames();
548        sb.append("" + cursor.getPosition() + " {\n");
549        int length = cols.length;
550        for (int i = 0; i < length; i++) {
551            String value;
552            try {
553                value = cursor.getString(i);
554            } catch (SQLiteException e) {
555                // assume that if the getString threw this exception then the column is not
556                // representable by a string, e.g. it is a BLOB.
557                value = "<unprintable>";
558            }
559            sb.append("   " + cols[i] + '=' + value + "\n");
560        }
561        sb.append("}\n");
562    }
563
564    /**
565     * Dump the contents of a Cursor's current row to a String.
566     *
567     * @param cursor the cursor to print
568     * @return a String that contains the dumped cursor row
569     */
570    public static String dumpCurrentRowToString(Cursor cursor) {
571        StringBuilder sb = new StringBuilder();
572        dumpCurrentRow(cursor, sb);
573        return sb.toString();
574    }
575
576    /**
577     * Reads a String out of a field in a Cursor and writes it to a Map.
578     *
579     * @param cursor The cursor to read from
580     * @param field The TEXT field to read
581     * @param values The {@link ContentValues} to put the value into, with the field as the key
582     */
583    public static void cursorStringToContentValues(Cursor cursor, String field,
584            ContentValues values) {
585        cursorStringToContentValues(cursor, field, values, field);
586    }
587
588    /**
589     * Reads a String out of a field in a Cursor and writes it to an InsertHelper.
590     *
591     * @param cursor The cursor to read from
592     * @param field The TEXT field to read
593     * @param inserter The InsertHelper to bind into
594     * @param index the index of the bind entry in the InsertHelper
595     */
596    public static void cursorStringToInsertHelper(Cursor cursor, String field,
597            InsertHelper inserter, int index) {
598        inserter.bind(index, cursor.getString(cursor.getColumnIndexOrThrow(field)));
599    }
600
601    /**
602     * Reads a String out of a field in a Cursor and writes it to a Map.
603     *
604     * @param cursor The cursor to read from
605     * @param field The TEXT field to read
606     * @param values The {@link ContentValues} to put the value into, with the field as the key
607     * @param key The key to store the value with in the map
608     */
609    public static void cursorStringToContentValues(Cursor cursor, String field,
610            ContentValues values, String key) {
611        values.put(key, cursor.getString(cursor.getColumnIndexOrThrow(field)));
612    }
613
614    /**
615     * Reads an Integer out of a field in a Cursor and writes it to a Map.
616     *
617     * @param cursor The cursor to read from
618     * @param field The INTEGER field to read
619     * @param values The {@link ContentValues} to put the value into, with the field as the key
620     */
621    public static void cursorIntToContentValues(Cursor cursor, String field, ContentValues values) {
622        cursorIntToContentValues(cursor, field, values, field);
623    }
624
625    /**
626     * Reads a Integer out of a field in a Cursor and writes it to a Map.
627     *
628     * @param cursor The cursor to read from
629     * @param field The INTEGER field to read
630     * @param values The {@link ContentValues} to put the value into, with the field as the key
631     * @param key The key to store the value with in the map
632     */
633    public static void cursorIntToContentValues(Cursor cursor, String field, ContentValues values,
634            String key) {
635        int colIndex = cursor.getColumnIndex(field);
636        if (!cursor.isNull(colIndex)) {
637            values.put(key, cursor.getInt(colIndex));
638        } else {
639            values.put(key, (Integer) null);
640        }
641    }
642
643    /**
644     * Reads a Long out of a field in a Cursor and writes it to a Map.
645     *
646     * @param cursor The cursor to read from
647     * @param field The INTEGER field to read
648     * @param values The {@link ContentValues} to put the value into, with the field as the key
649     */
650    public static void cursorLongToContentValues(Cursor cursor, String field, ContentValues values)
651    {
652        cursorLongToContentValues(cursor, field, values, field);
653    }
654
655    /**
656     * Reads a Long out of a field in a Cursor and writes it to a Map.
657     *
658     * @param cursor The cursor to read from
659     * @param field The INTEGER field to read
660     * @param values The {@link ContentValues} to put the value into
661     * @param key The key to store the value with in the map
662     */
663    public static void cursorLongToContentValues(Cursor cursor, String field, ContentValues values,
664            String key) {
665        int colIndex = cursor.getColumnIndex(field);
666        if (!cursor.isNull(colIndex)) {
667            Long value = Long.valueOf(cursor.getLong(colIndex));
668            values.put(key, value);
669        } else {
670            values.put(key, (Long) null);
671        }
672    }
673
674    /**
675     * Reads a Double out of a field in a Cursor and writes it to a Map.
676     *
677     * @param cursor The cursor to read from
678     * @param field The REAL field to read
679     * @param values The {@link ContentValues} to put the value into
680     */
681    public static void cursorDoubleToCursorValues(Cursor cursor, String field, ContentValues values)
682    {
683        cursorDoubleToContentValues(cursor, field, values, field);
684    }
685
686    /**
687     * Reads a Double out of a field in a Cursor and writes it to a Map.
688     *
689     * @param cursor The cursor to read from
690     * @param field The REAL field to read
691     * @param values The {@link ContentValues} to put the value into
692     * @param key The key to store the value with in the map
693     */
694    public static void cursorDoubleToContentValues(Cursor cursor, String field,
695            ContentValues values, String key) {
696        int colIndex = cursor.getColumnIndex(field);
697        if (!cursor.isNull(colIndex)) {
698            values.put(key, cursor.getDouble(colIndex));
699        } else {
700            values.put(key, (Double) null);
701        }
702    }
703
704    /**
705     * Read the entire contents of a cursor row and store them in a ContentValues.
706     *
707     * @param cursor the cursor to read from.
708     * @param values the {@link ContentValues} to put the row into.
709     */
710    public static void cursorRowToContentValues(Cursor cursor, ContentValues values) {
711        AbstractWindowedCursor awc =
712                (cursor instanceof AbstractWindowedCursor) ? (AbstractWindowedCursor) cursor : null;
713
714        String[] columns = cursor.getColumnNames();
715        int length = columns.length;
716        for (int i = 0; i < length; i++) {
717            if (awc != null && awc.isBlob(i)) {
718                values.put(columns[i], cursor.getBlob(i));
719            } else {
720                values.put(columns[i], cursor.getString(i));
721            }
722        }
723    }
724
725    /**
726     * Picks a start position for {@link Cursor#fillWindow} such that the
727     * window will contain the requested row and a useful range of rows
728     * around it.
729     *
730     * When the data set is too large to fit in a cursor window, seeking the
731     * cursor can become a very expensive operation since we have to run the
732     * query again when we move outside the bounds of the current window.
733     *
734     * We try to choose a start position for the cursor window such that
735     * 1/3 of the window's capacity is used to hold rows before the requested
736     * position and 2/3 of the window's capacity is used to hold rows after the
737     * requested position.
738     *
739     * @param cursorPosition The row index of the row we want to get.
740     * @param cursorWindowCapacity The estimated number of rows that can fit in
741     * a cursor window, or 0 if unknown.
742     * @return The recommended start position, always less than or equal to
743     * the requested row.
744     * @hide
745     */
746    public static int cursorPickFillWindowStartPosition(
747            int cursorPosition, int cursorWindowCapacity) {
748        return Math.max(cursorPosition - cursorWindowCapacity / 3, 0);
749    }
750
751    /**
752     * Query the table for the number of rows in the table.
753     * @param db the database the table is in
754     * @param table the name of the table to query
755     * @return the number of rows in the table
756     */
757    public static long queryNumEntries(SQLiteDatabase db, String table) {
758        return queryNumEntries(db, table, null, null);
759    }
760
761    /**
762     * Query the table for the number of rows in the table.
763     * @param db the database the table is in
764     * @param table the name of the table to query
765     * @param selection A filter declaring which rows to return,
766     *              formatted as an SQL WHERE clause (excluding the WHERE itself).
767     *              Passing null will count all rows for the given table
768     * @return the number of rows in the table filtered by the selection
769     */
770    public static long queryNumEntries(SQLiteDatabase db, String table, String selection) {
771        return queryNumEntries(db, table, selection, null);
772    }
773
774    /**
775     * Query the table for the number of rows in the table.
776     * @param db the database the table is in
777     * @param table the name of the table to query
778     * @param selection A filter declaring which rows to return,
779     *              formatted as an SQL WHERE clause (excluding the WHERE itself).
780     *              Passing null will count all rows for the given table
781     * @param selectionArgs You may include ?s in selection,
782     *              which will be replaced by the values from selectionArgs,
783     *              in order that they appear in the selection.
784     *              The values will be bound as Strings.
785     * @return the number of rows in the table filtered by the selection
786     */
787    public static long queryNumEntries(SQLiteDatabase db, String table, String selection,
788            String[] selectionArgs) {
789        String s = (!TextUtils.isEmpty(selection)) ? " where " + selection : "";
790        return longForQuery(db, "select count(*) from " + table + s,
791                    selectionArgs);
792    }
793
794    /**
795     * Query the table to check whether a table is empty or not
796     * @param db the database the table is in
797     * @param table the name of the table to query
798     * @return True if the table is empty
799     * @hide
800     */
801    public static boolean queryIsEmpty(SQLiteDatabase db, String table) {
802        long isEmpty = longForQuery(db, "select exists(select 1 from " + table + ")", null);
803        return isEmpty == 0;
804    }
805
806    /**
807     * Utility method to run the query on the db and return the value in the
808     * first column of the first row.
809     */
810    public static long longForQuery(SQLiteDatabase db, String query, String[] selectionArgs) {
811        SQLiteStatement prog = db.compileStatement(query);
812        try {
813            return longForQuery(prog, selectionArgs);
814        } finally {
815            prog.close();
816        }
817    }
818
819    /**
820     * Utility method to run the pre-compiled query and return the value in the
821     * first column of the first row.
822     */
823    public static long longForQuery(SQLiteStatement prog, String[] selectionArgs) {
824        prog.bindAllArgsAsStrings(selectionArgs);
825        return prog.simpleQueryForLong();
826    }
827
828    /**
829     * Utility method to run the query on the db and return the value in the
830     * first column of the first row.
831     */
832    public static String stringForQuery(SQLiteDatabase db, String query, String[] selectionArgs) {
833        SQLiteStatement prog = db.compileStatement(query);
834        try {
835            return stringForQuery(prog, selectionArgs);
836        } finally {
837            prog.close();
838        }
839    }
840
841    /**
842     * Utility method to run the pre-compiled query and return the value in the
843     * first column of the first row.
844     */
845    public static String stringForQuery(SQLiteStatement prog, String[] selectionArgs) {
846        prog.bindAllArgsAsStrings(selectionArgs);
847        return prog.simpleQueryForString();
848    }
849
850    /**
851     * Utility method to run the query on the db and return the blob value in the
852     * first column of the first row.
853     *
854     * @return A read-only file descriptor for a copy of the blob value.
855     */
856    public static ParcelFileDescriptor blobFileDescriptorForQuery(SQLiteDatabase db,
857            String query, String[] selectionArgs) {
858        SQLiteStatement prog = db.compileStatement(query);
859        try {
860            return blobFileDescriptorForQuery(prog, selectionArgs);
861        } finally {
862            prog.close();
863        }
864    }
865
866    /**
867     * Utility method to run the pre-compiled query and return the blob value in the
868     * first column of the first row.
869     *
870     * @return A read-only file descriptor for a copy of the blob value.
871     */
872    public static ParcelFileDescriptor blobFileDescriptorForQuery(SQLiteStatement prog,
873            String[] selectionArgs) {
874        prog.bindAllArgsAsStrings(selectionArgs);
875        return prog.simpleQueryForBlobFileDescriptor();
876    }
877
878    /**
879     * Reads a String out of a column in a Cursor and writes it to a ContentValues.
880     * Adds nothing to the ContentValues if the column isn't present or if its value is null.
881     *
882     * @param cursor The cursor to read from
883     * @param column The column to read
884     * @param values The {@link ContentValues} to put the value into
885     */
886    public static void cursorStringToContentValuesIfPresent(Cursor cursor, ContentValues values,
887            String column) {
888        final int index = cursor.getColumnIndex(column);
889        if (index != -1 && !cursor.isNull(index)) {
890            values.put(column, cursor.getString(index));
891        }
892    }
893
894    /**
895     * Reads a Long out of a column in a Cursor and writes it to a ContentValues.
896     * Adds nothing to the ContentValues if the column isn't present or if its value is null.
897     *
898     * @param cursor The cursor to read from
899     * @param column The column to read
900     * @param values The {@link ContentValues} to put the value into
901     */
902    public static void cursorLongToContentValuesIfPresent(Cursor cursor, ContentValues values,
903            String column) {
904        final int index = cursor.getColumnIndex(column);
905        if (index != -1 && !cursor.isNull(index)) {
906            values.put(column, cursor.getLong(index));
907        }
908    }
909
910    /**
911     * Reads a Short out of a column in a Cursor and writes it to a ContentValues.
912     * Adds nothing to the ContentValues if the column isn't present or if its value is null.
913     *
914     * @param cursor The cursor to read from
915     * @param column The column to read
916     * @param values The {@link ContentValues} to put the value into
917     */
918    public static void cursorShortToContentValuesIfPresent(Cursor cursor, ContentValues values,
919            String column) {
920        final int index = cursor.getColumnIndex(column);
921        if (index != -1 && !cursor.isNull(index)) {
922            values.put(column, cursor.getShort(index));
923        }
924    }
925
926    /**
927     * Reads a Integer out of a column in a Cursor and writes it to a ContentValues.
928     * Adds nothing to the ContentValues if the column isn't present or if its value is null.
929     *
930     * @param cursor The cursor to read from
931     * @param column The column to read
932     * @param values The {@link ContentValues} to put the value into
933     */
934    public static void cursorIntToContentValuesIfPresent(Cursor cursor, ContentValues values,
935            String column) {
936        final int index = cursor.getColumnIndex(column);
937        if (index != -1 && !cursor.isNull(index)) {
938            values.put(column, cursor.getInt(index));
939        }
940    }
941
942    /**
943     * Reads a Float out of a column in a Cursor and writes it to a ContentValues.
944     * Adds nothing to the ContentValues if the column isn't present or if its value is null.
945     *
946     * @param cursor The cursor to read from
947     * @param column The column to read
948     * @param values The {@link ContentValues} to put the value into
949     */
950    public static void cursorFloatToContentValuesIfPresent(Cursor cursor, ContentValues values,
951            String column) {
952        final int index = cursor.getColumnIndex(column);
953        if (index != -1 && !cursor.isNull(index)) {
954            values.put(column, cursor.getFloat(index));
955        }
956    }
957
958    /**
959     * Reads a Double out of a column in a Cursor and writes it to a ContentValues.
960     * Adds nothing to the ContentValues if the column isn't present or if its value is null.
961     *
962     * @param cursor The cursor to read from
963     * @param column The column to read
964     * @param values The {@link ContentValues} to put the value into
965     */
966    public static void cursorDoubleToContentValuesIfPresent(Cursor cursor, ContentValues values,
967            String column) {
968        final int index = cursor.getColumnIndex(column);
969        if (index != -1 && !cursor.isNull(index)) {
970            values.put(column, cursor.getDouble(index));
971        }
972    }
973
974    /**
975     * This class allows users to do multiple inserts into a table using
976     * the same statement.
977     * <p>
978     * This class is not thread-safe.
979     * </p>
980     *
981     * @deprecated Use {@link SQLiteStatement} instead.
982     */
983    @Deprecated
984    public static class InsertHelper {
985        private final SQLiteDatabase mDb;
986        private final String mTableName;
987        private HashMap<String, Integer> mColumns;
988        private String mInsertSQL = null;
989        private SQLiteStatement mInsertStatement = null;
990        private SQLiteStatement mReplaceStatement = null;
991        private SQLiteStatement mPreparedStatement = null;
992
993        /**
994         * {@hide}
995         *
996         * These are the columns returned by sqlite's "PRAGMA
997         * table_info(...)" command that we depend on.
998         */
999        public static final int TABLE_INFO_PRAGMA_COLUMNNAME_INDEX = 1;
1000
1001        /**
1002         * This field was accidentally exposed in earlier versions of the platform
1003         * so we can hide it but we can't remove it.
1004         *
1005         * @hide
1006         */
1007        public static final int TABLE_INFO_PRAGMA_DEFAULT_INDEX = 4;
1008
1009        /**
1010         * @param db the SQLiteDatabase to insert into
1011         * @param tableName the name of the table to insert into
1012         */
1013        public InsertHelper(SQLiteDatabase db, String tableName) {
1014            mDb = db;
1015            mTableName = tableName;
1016        }
1017
1018        private void buildSQL() throws SQLException {
1019            StringBuilder sb = new StringBuilder(128);
1020            sb.append("INSERT INTO ");
1021            sb.append(mTableName);
1022            sb.append(" (");
1023
1024            StringBuilder sbv = new StringBuilder(128);
1025            sbv.append("VALUES (");
1026
1027            int i = 1;
1028            Cursor cur = null;
1029            try {
1030                cur = mDb.rawQuery("PRAGMA table_info(" + mTableName + ")", null);
1031                mColumns = new HashMap<String, Integer>(cur.getCount());
1032                while (cur.moveToNext()) {
1033                    String columnName = cur.getString(TABLE_INFO_PRAGMA_COLUMNNAME_INDEX);
1034                    String defaultValue = cur.getString(TABLE_INFO_PRAGMA_DEFAULT_INDEX);
1035
1036                    mColumns.put(columnName, i);
1037                    sb.append("'");
1038                    sb.append(columnName);
1039                    sb.append("'");
1040
1041                    if (defaultValue == null) {
1042                        sbv.append("?");
1043                    } else {
1044                        sbv.append("COALESCE(?, ");
1045                        sbv.append(defaultValue);
1046                        sbv.append(")");
1047                    }
1048
1049                    sb.append(i == cur.getCount() ? ") " : ", ");
1050                    sbv.append(i == cur.getCount() ? ");" : ", ");
1051                    ++i;
1052                }
1053            } finally {
1054                if (cur != null) cur.close();
1055            }
1056
1057            sb.append(sbv);
1058
1059            mInsertSQL = sb.toString();
1060            if (DEBUG) Log.v(TAG, "insert statement is " + mInsertSQL);
1061        }
1062
1063        private SQLiteStatement getStatement(boolean allowReplace) throws SQLException {
1064            if (allowReplace) {
1065                if (mReplaceStatement == null) {
1066                    if (mInsertSQL == null) buildSQL();
1067                    // chop "INSERT" off the front and prepend "INSERT OR REPLACE" instead.
1068                    String replaceSQL = "INSERT OR REPLACE" + mInsertSQL.substring(6);
1069                    mReplaceStatement = mDb.compileStatement(replaceSQL);
1070                }
1071                return mReplaceStatement;
1072            } else {
1073                if (mInsertStatement == null) {
1074                    if (mInsertSQL == null) buildSQL();
1075                    mInsertStatement = mDb.compileStatement(mInsertSQL);
1076                }
1077                return mInsertStatement;
1078            }
1079        }
1080
1081        /**
1082         * Performs an insert, adding a new row with the given values.
1083         *
1084         * @param values the set of values with which  to populate the
1085         * new row
1086         * @param allowReplace if true, the statement does "INSERT OR
1087         *   REPLACE" instead of "INSERT", silently deleting any
1088         *   previously existing rows that would cause a conflict
1089         *
1090         * @return the row ID of the newly inserted row, or -1 if an
1091         * error occurred
1092         */
1093        private long insertInternal(ContentValues values, boolean allowReplace) {
1094            // Start a transaction even though we don't really need one.
1095            // This is to help maintain compatibility with applications that
1096            // access InsertHelper from multiple threads even though they never should have.
1097            // The original code used to lock the InsertHelper itself which was prone
1098            // to deadlocks.  Starting a transaction achieves the same mutual exclusion
1099            // effect as grabbing a lock but without the potential for deadlocks.
1100            mDb.beginTransactionNonExclusive();
1101            try {
1102                SQLiteStatement stmt = getStatement(allowReplace);
1103                stmt.clearBindings();
1104                if (DEBUG) Log.v(TAG, "--- inserting in table " + mTableName);
1105                for (Map.Entry<String, Object> e: values.valueSet()) {
1106                    final String key = e.getKey();
1107                    int i = getColumnIndex(key);
1108                    DatabaseUtils.bindObjectToProgram(stmt, i, e.getValue());
1109                    if (DEBUG) {
1110                        Log.v(TAG, "binding " + e.getValue() + " to column " +
1111                              i + " (" + key + ")");
1112                    }
1113                }
1114                long result = stmt.executeInsert();
1115                mDb.setTransactionSuccessful();
1116                return result;
1117            } catch (SQLException e) {
1118                Log.e(TAG, "Error inserting " + values + " into table  " + mTableName, e);
1119                return -1;
1120            } finally {
1121                mDb.endTransaction();
1122            }
1123        }
1124
1125        /**
1126         * Returns the index of the specified column. This is index is suitagble for use
1127         * in calls to bind().
1128         * @param key the column name
1129         * @return the index of the column
1130         */
1131        public int getColumnIndex(String key) {
1132            getStatement(false);
1133            final Integer index = mColumns.get(key);
1134            if (index == null) {
1135                throw new IllegalArgumentException("column '" + key + "' is invalid");
1136            }
1137            return index;
1138        }
1139
1140        /**
1141         * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1142         * without a matching execute() must have already have been called.
1143         * @param index the index of the slot to which to bind
1144         * @param value the value to bind
1145         */
1146        public void bind(int index, double value) {
1147            mPreparedStatement.bindDouble(index, value);
1148        }
1149
1150        /**
1151         * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1152         * without a matching execute() must have already have been called.
1153         * @param index the index of the slot to which to bind
1154         * @param value the value to bind
1155         */
1156        public void bind(int index, float value) {
1157            mPreparedStatement.bindDouble(index, value);
1158        }
1159
1160        /**
1161         * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1162         * without a matching execute() must have already have been called.
1163         * @param index the index of the slot to which to bind
1164         * @param value the value to bind
1165         */
1166        public void bind(int index, long value) {
1167            mPreparedStatement.bindLong(index, value);
1168        }
1169
1170        /**
1171         * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1172         * without a matching execute() must have already have been called.
1173         * @param index the index of the slot to which to bind
1174         * @param value the value to bind
1175         */
1176        public void bind(int index, int value) {
1177            mPreparedStatement.bindLong(index, value);
1178        }
1179
1180        /**
1181         * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1182         * without a matching execute() must have already have been called.
1183         * @param index the index of the slot to which to bind
1184         * @param value the value to bind
1185         */
1186        public void bind(int index, boolean value) {
1187            mPreparedStatement.bindLong(index, value ? 1 : 0);
1188        }
1189
1190        /**
1191         * Bind null to an index. A prepareForInsert() or prepareForReplace()
1192         * without a matching execute() must have already have been called.
1193         * @param index the index of the slot to which to bind
1194         */
1195        public void bindNull(int index) {
1196            mPreparedStatement.bindNull(index);
1197        }
1198
1199        /**
1200         * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1201         * without a matching execute() must have already have been called.
1202         * @param index the index of the slot to which to bind
1203         * @param value the value to bind
1204         */
1205        public void bind(int index, byte[] value) {
1206            if (value == null) {
1207                mPreparedStatement.bindNull(index);
1208            } else {
1209                mPreparedStatement.bindBlob(index, value);
1210            }
1211        }
1212
1213        /**
1214         * Bind the value to an index. A prepareForInsert() or prepareForReplace()
1215         * without a matching execute() must have already have been called.
1216         * @param index the index of the slot to which to bind
1217         * @param value the value to bind
1218         */
1219        public void bind(int index, String value) {
1220            if (value == null) {
1221                mPreparedStatement.bindNull(index);
1222            } else {
1223                mPreparedStatement.bindString(index, value);
1224            }
1225        }
1226
1227        /**
1228         * Performs an insert, adding a new row with the given values.
1229         * If the table contains conflicting rows, an error is
1230         * returned.
1231         *
1232         * @param values the set of values with which to populate the
1233         * new row
1234         *
1235         * @return the row ID of the newly inserted row, or -1 if an
1236         * error occurred
1237         */
1238        public long insert(ContentValues values) {
1239            return insertInternal(values, false);
1240        }
1241
1242        /**
1243         * Execute the previously prepared insert or replace using the bound values
1244         * since the last call to prepareForInsert or prepareForReplace.
1245         *
1246         * <p>Note that calling bind() and then execute() is not thread-safe. The only thread-safe
1247         * way to use this class is to call insert() or replace().
1248         *
1249         * @return the row ID of the newly inserted row, or -1 if an
1250         * error occurred
1251         */
1252        public long execute() {
1253            if (mPreparedStatement == null) {
1254                throw new IllegalStateException("you must prepare this inserter before calling "
1255                        + "execute");
1256            }
1257            try {
1258                if (DEBUG) Log.v(TAG, "--- doing insert or replace in table " + mTableName);
1259                return mPreparedStatement.executeInsert();
1260            } catch (SQLException e) {
1261                Log.e(TAG, "Error executing InsertHelper with table " + mTableName, e);
1262                return -1;
1263            } finally {
1264                // you can only call this once per prepare
1265                mPreparedStatement = null;
1266            }
1267        }
1268
1269        /**
1270         * Prepare the InsertHelper for an insert. The pattern for this is:
1271         * <ul>
1272         * <li>prepareForInsert()
1273         * <li>bind(index, value);
1274         * <li>bind(index, value);
1275         * <li>...
1276         * <li>bind(index, value);
1277         * <li>execute();
1278         * </ul>
1279         */
1280        public void prepareForInsert() {
1281            mPreparedStatement = getStatement(false);
1282            mPreparedStatement.clearBindings();
1283        }
1284
1285        /**
1286         * Prepare the InsertHelper for a replace. The pattern for this is:
1287         * <ul>
1288         * <li>prepareForReplace()
1289         * <li>bind(index, value);
1290         * <li>bind(index, value);
1291         * <li>...
1292         * <li>bind(index, value);
1293         * <li>execute();
1294         * </ul>
1295         */
1296        public void prepareForReplace() {
1297            mPreparedStatement = getStatement(true);
1298            mPreparedStatement.clearBindings();
1299        }
1300
1301        /**
1302         * Performs an insert, adding a new row with the given values.
1303         * If the table contains conflicting rows, they are deleted
1304         * and replaced with the new row.
1305         *
1306         * @param values the set of values with which to populate the
1307         * new row
1308         *
1309         * @return the row ID of the newly inserted row, or -1 if an
1310         * error occurred
1311         */
1312        public long replace(ContentValues values) {
1313            return insertInternal(values, true);
1314        }
1315
1316        /**
1317         * Close this object and release any resources associated with
1318         * it.  The behavior of calling <code>insert()</code> after
1319         * calling this method is undefined.
1320         */
1321        public void close() {
1322            if (mInsertStatement != null) {
1323                mInsertStatement.close();
1324                mInsertStatement = null;
1325            }
1326            if (mReplaceStatement != null) {
1327                mReplaceStatement.close();
1328                mReplaceStatement = null;
1329            }
1330            mInsertSQL = null;
1331            mColumns = null;
1332        }
1333    }
1334
1335    /**
1336     * Creates a db and populates it with the sql statements in sqlStatements.
1337     *
1338     * @param context the context to use to create the db
1339     * @param dbName the name of the db to create
1340     * @param dbVersion the version to set on the db
1341     * @param sqlStatements the statements to use to populate the db. This should be a single string
1342     *   of the form returned by sqlite3's <tt>.dump</tt> command (statements separated by
1343     *   semicolons)
1344     */
1345    static public void createDbFromSqlStatements(
1346            Context context, String dbName, int dbVersion, String sqlStatements) {
1347        SQLiteDatabase db = context.openOrCreateDatabase(dbName, 0, null);
1348        // TODO: this is not quite safe since it assumes that all semicolons at the end of a line
1349        // terminate statements. It is possible that a text field contains ;\n. We will have to fix
1350        // this if that turns out to be a problem.
1351        String[] statements = TextUtils.split(sqlStatements, ";\n");
1352        for (String statement : statements) {
1353            if (TextUtils.isEmpty(statement)) continue;
1354            db.execSQL(statement);
1355        }
1356        db.setVersion(dbVersion);
1357        db.close();
1358    }
1359
1360    /**
1361     * Returns one of the following which represent the type of the given SQL statement.
1362     * <ol>
1363     *   <li>{@link #STATEMENT_SELECT}</li>
1364     *   <li>{@link #STATEMENT_UPDATE}</li>
1365     *   <li>{@link #STATEMENT_ATTACH}</li>
1366     *   <li>{@link #STATEMENT_BEGIN}</li>
1367     *   <li>{@link #STATEMENT_COMMIT}</li>
1368     *   <li>{@link #STATEMENT_ABORT}</li>
1369     *   <li>{@link #STATEMENT_OTHER}</li>
1370     * </ol>
1371     * @param sql the SQL statement whose type is returned by this method
1372     * @return one of the values listed above
1373     */
1374    public static int getSqlStatementType(String sql) {
1375        sql = sql.trim();
1376        if (sql.length() < 3) {
1377            return STATEMENT_OTHER;
1378        }
1379        String prefixSql = sql.substring(0, 3).toUpperCase(Locale.ROOT);
1380        if (prefixSql.equals("SEL")) {
1381            return STATEMENT_SELECT;
1382        } else if (prefixSql.equals("INS") ||
1383                prefixSql.equals("UPD") ||
1384                prefixSql.equals("REP") ||
1385                prefixSql.equals("DEL")) {
1386            return STATEMENT_UPDATE;
1387        } else if (prefixSql.equals("ATT")) {
1388            return STATEMENT_ATTACH;
1389        } else if (prefixSql.equals("COM")) {
1390            return STATEMENT_COMMIT;
1391        } else if (prefixSql.equals("END")) {
1392            return STATEMENT_COMMIT;
1393        } else if (prefixSql.equals("ROL")) {
1394            return STATEMENT_ABORT;
1395        } else if (prefixSql.equals("BEG")) {
1396            return STATEMENT_BEGIN;
1397        } else if (prefixSql.equals("PRA")) {
1398            return STATEMENT_PRAGMA;
1399        } else if (prefixSql.equals("CRE") || prefixSql.equals("DRO") ||
1400                prefixSql.equals("ALT")) {
1401            return STATEMENT_DDL;
1402        } else if (prefixSql.equals("ANA") || prefixSql.equals("DET")) {
1403            return STATEMENT_UNPREPARED;
1404        }
1405        return STATEMENT_OTHER;
1406    }
1407
1408    /**
1409     * Appends one set of selection args to another. This is useful when adding a selection
1410     * argument to a user provided set.
1411     */
1412    public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) {
1413        if (originalValues == null || originalValues.length == 0) {
1414            return newValues;
1415        }
1416        String[] result = new String[originalValues.length + newValues.length ];
1417        System.arraycopy(originalValues, 0, result, 0, originalValues.length);
1418        System.arraycopy(newValues, 0, result, originalValues.length, newValues.length);
1419        return result;
1420    }
1421
1422    /**
1423     * Returns column index of "_id" column, or -1 if not found.
1424     * @hide
1425     */
1426    public static int findRowIdColumnIndex(String[] columnNames) {
1427        int length = columnNames.length;
1428        for (int i = 0; i < length; i++) {
1429            if (columnNames[i].equals("_id")) {
1430                return i;
1431            }
1432        }
1433        return -1;
1434    }
1435}
1436