All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.activeandroid.util.SQLiteUtils Maven / Gradle / Ivy

There is a newer version: 3.1.5
Show newest version
package com.activeandroid.util;

/*
 * Copyright (C) 2010 Michael Pardo
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import android.database.Cursor;
import android.os.Build;
import android.text.TextUtils;

import com.activeandroid.Cache;
import com.activeandroid.Model;
import com.activeandroid.TableInfo;
import com.activeandroid.annotation.Column;
import com.activeandroid.annotation.Column.ConflictAction;
import com.activeandroid.rxschedulers.AndroidSchedulers;
import com.activeandroid.serializer.TypeSerializer;
import com.activeandroid.sqlbrite.QueryObservable;
import com.activeandroid.sqlbrite.SqlBrite;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import rx.Observable;
import rx.functions.Func1;
import rx.schedulers.Schedulers;

public final class SQLiteUtils {
    //////////////////////////////////////////////////////////////////////////////////////
    // ENUMERATIONS
    //////////////////////////////////////////////////////////////////////////////////////

    public enum SQLiteType {
        INTEGER, REAL, TEXT, BLOB
    }

    //////////////////////////////////////////////////////////////////////////////////////
    // PUBLIC CONSTANTS
    //////////////////////////////////////////////////////////////////////////////////////

    public static final boolean FOREIGN_KEYS_SUPPORTED = Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO;

    //////////////////////////////////////////////////////////////////////////////////////
    // PRIVATE CONTSANTS
    //////////////////////////////////////////////////////////////////////////////////////

    @SuppressWarnings("serial")
    private static final HashMap, SQLiteType> TYPE_MAP = new HashMap, SQLiteType>() {
        {
            put(byte.class, SQLiteType.INTEGER);
            put(short.class, SQLiteType.INTEGER);
            put(int.class, SQLiteType.INTEGER);
            put(long.class, SQLiteType.INTEGER);
            put(float.class, SQLiteType.REAL);
            put(double.class, SQLiteType.REAL);
            put(boolean.class, SQLiteType.INTEGER);
            put(char.class, SQLiteType.TEXT);
            put(byte[].class, SQLiteType.BLOB);
            put(Byte.class, SQLiteType.INTEGER);
            put(Short.class, SQLiteType.INTEGER);
            put(Integer.class, SQLiteType.INTEGER);
            put(Long.class, SQLiteType.INTEGER);
            put(Float.class, SQLiteType.REAL);
            put(Double.class, SQLiteType.REAL);
            put(Boolean.class, SQLiteType.INTEGER);
            put(Character.class, SQLiteType.TEXT);
            put(String.class, SQLiteType.TEXT);
            put(Byte[].class, SQLiteType.BLOB);
        }
    };

    //////////////////////////////////////////////////////////////////////////////////////
    // PRIVATE MEMBERS
    //////////////////////////////////////////////////////////////////////////////////////

    private static HashMap> sIndexGroupMap;
    private static HashMap> sUniqueGroupMap;
    private static HashMap sOnUniqueConflictsMap;

    //////////////////////////////////////////////////////////////////////////////////////
    // PUBLIC METHODS
    //////////////////////////////////////////////////////////////////////////////////////

    public static void execSql(String sql) {
        Cache.openDatabase().execute(sql);
    }

    public static void execSql(String sql, Object[] bindArgs) {
        Cache.openDatabase().execute(sql, bindArgs);
    }

    public static  List rawQuery(Class type, String sql, String[] selectionArgs) {
        Cursor cursor = Cache.openDatabase().query(sql, selectionArgs);
        List entities = processCursor(type, cursor);
        cursor.close();

        return entities;
    }

    public static  Observable rawRxQuery(final Class type, String sql, String[] selectionArgs) {

        TableInfo tableInfo = new TableInfo(type);
        QueryObservable query = Cache.openDatabase().createQuery(tableInfo.getTableName(), sql, selectionArgs);

        return query.subscribeOn(Schedulers.io())
                .map(new Func1() {
                    @Override
                    public T call(SqlBrite.Query query) {
                        try {

                            Cursor cursor = query.run();
                            return (T) processCursor(type, cursor);

                        } catch (Exception ex) {
                            ex.printStackTrace();
                        }

                        return null;

                    }
                })
                .observeOn(AndroidSchedulers.mainThread());

    }

    public static int intQuery(final String sql, final String[] selectionArgs) {
        final Cursor cursor = Cache.openDatabase().query(sql, selectionArgs);
        final int number = processIntCursor(cursor);
        cursor.close();

        return number;
    }

    public static  T rawQuerySingle(Class type, String sql, String[] selectionArgs) {
        List entities = rawQuery(type, sql, selectionArgs);

        if (entities.size() > 0) {
            return entities.get(0);
        }

        return null;
    }

    // Database creation

    public static ArrayList createUniqueDefinition(TableInfo tableInfo) {
        final ArrayList definitions = new ArrayList();
        sUniqueGroupMap = new HashMap>();
        sOnUniqueConflictsMap = new HashMap();

        for (Field field : tableInfo.getFields()) {
            createUniqueColumnDefinition(tableInfo, field);
        }

        if (sUniqueGroupMap.isEmpty()) {
            return definitions;
        }

        Set keySet = sUniqueGroupMap.keySet();
        for (String key : keySet) {
            List group = sUniqueGroupMap.get(key);
            ConflictAction conflictAction = sOnUniqueConflictsMap.get(key);

            definitions.add(String.format("UNIQUE (%s) ON CONFLICT %s",
                    TextUtils.join(", ", group), conflictAction.toString()));
        }

        return definitions;
    }

    public static void createUniqueColumnDefinition(TableInfo tableInfo, Field field) {
        final String name = tableInfo.getColumnName(field);
        final Column column = field.getAnnotation(Column.class);

        if (field.getName().equals("mId")) {
            return;
        }

        String[] groups = column.uniqueGroups();
        ConflictAction[] conflictActions = column.onUniqueConflicts();
        if (groups.length != conflictActions.length)
            return;

        for (int i = 0; i < groups.length; i++) {
            String group = groups[i];
            ConflictAction conflictAction = conflictActions[i];

            if (TextUtils.isEmpty(group))
                continue;

            List list = sUniqueGroupMap.get(group);
            if (list == null) {
                list = new ArrayList();
            }
            list.add(name);

            sUniqueGroupMap.put(group, list);
            sOnUniqueConflictsMap.put(group, conflictAction);
        }
    }

    public static String[] createIndexDefinition(TableInfo tableInfo) {
        final ArrayList definitions = new ArrayList();
        sIndexGroupMap = new HashMap>();

        for (Field field : tableInfo.getFields()) {
            createIndexColumnDefinition(tableInfo, field);
        }

        if (sIndexGroupMap.isEmpty()) {
            return new String[0];
        }

        for (Map.Entry> entry : sIndexGroupMap.entrySet()) {
            definitions.add(String.format("CREATE INDEX IF NOT EXISTS %s on %s(%s);",
                    "index_" + tableInfo.getTableName() + "_" + entry.getKey(),
                    tableInfo.getTableName(), TextUtils.join(", ", entry.getValue())));
        }

        return definitions.toArray(new String[definitions.size()]);
    }

    public static void createIndexColumnDefinition(TableInfo tableInfo, Field field) {
        final String name = tableInfo.getColumnName(field);
        final Column column = field.getAnnotation(Column.class);

        if (field.getName().equals("mId")) {
            return;
        }

        if (column.index()) {
            List list = new ArrayList();
            list.add(name);
            sIndexGroupMap.put(name, list);
        }

        String[] groups = column.indexGroups();
        for (String group : groups) {
            if (TextUtils.isEmpty(group))
                continue;

            List list = sIndexGroupMap.get(group);
            if (list == null) {
                list = new ArrayList();
            }

            list.add(name);
            sIndexGroupMap.put(group, list);
        }
    }

    public static String createTableDefinition(TableInfo tableInfo) {
        final ArrayList definitions = new ArrayList();

        for (Field field : tableInfo.getFields()) {
            String definition = createColumnDefinition(tableInfo, field);
            if (!TextUtils.isEmpty(definition)) {
                definitions.add(definition);
            }
        }

        definitions.addAll(createUniqueDefinition(tableInfo));

        return String.format("CREATE TABLE IF NOT EXISTS %s (%s);", tableInfo.getTableName(),
                TextUtils.join(", ", definitions));
    }

    @SuppressWarnings("unchecked")
    public static String createColumnDefinition(TableInfo tableInfo, Field field) {
        StringBuilder definition = new StringBuilder();

        Class type = field.getType();
        final String name = tableInfo.getColumnName(field);
        final TypeSerializer typeSerializer = Cache.getParserForType(field.getType());
        final Column column = field.getAnnotation(Column.class);

        if (typeSerializer != null) {
            type = typeSerializer.getSerializedType();
        }

        if (TYPE_MAP.containsKey(type)) {
            definition.append(name);
            definition.append(" ");
            definition.append(TYPE_MAP.get(type).toString());
        } else if (ReflectionUtils.isModel(type)) {
            definition.append(name);
            definition.append(" ");
            definition.append(SQLiteType.INTEGER.toString());
        } else if (ReflectionUtils.isSubclassOf(type, Enum.class)) {
            definition.append(name);
            definition.append(" ");
            definition.append(SQLiteType.TEXT.toString());
        }

        if (!TextUtils.isEmpty(definition)) {

            if (name.equals(tableInfo.getIdName())) {
                definition.append(" PRIMARY KEY AUTOINCREMENT");
            } else if (column != null) {
                if (column.length() > -1) {
                    definition.append("(");
                    definition.append(column.length());
                    definition.append(")");
                }

                if (column.notNull()) {
                    definition.append(" NOT NULL ON CONFLICT ");
                    definition.append(column.onNullConflict().toString());
                }

                if (column.unique()) {
                    definition.append(" UNIQUE ON CONFLICT ");
                    definition.append(column.onUniqueConflict().toString());
                }
            }

            if (FOREIGN_KEYS_SUPPORTED && ReflectionUtils.isModel(type)) {
                definition.append(" REFERENCES ");
                definition.append(Cache.getTableInfo((Class) type).getTableName());
                definition.append("(" + tableInfo.getIdName() + ")");
                definition.append(" ON DELETE ");
                definition.append(column.onDelete().toString().replace("_", " "));
                definition.append(" ON UPDATE ");
                definition.append(column.onUpdate().toString().replace("_", " "));
            }
        } else {
            Log.e("No type mapping for: " + type.toString());
        }

        return definition.toString();
    }

    @SuppressWarnings("unchecked")
    public static  List processCursor(Class type, Cursor cursor) {
        TableInfo tableInfo = Cache.getTableInfo(type);
        String idName = tableInfo.getIdName();
        final List entities = new ArrayList();

        try {
            Constructor entityConstructor = type.getConstructor();

            if (cursor.moveToFirst()) {
                /**
                 * Obtain the columns ordered to fix issue #106 (https://github.com/pardom/ActiveAndroid/issues/106)
                 * when the cursor have multiple columns with same name obtained from join tables.
                 */
                List columnsOrdered = new ArrayList(Arrays.asList(cursor.getColumnNames()));
                do {
                    Model entity = Cache.getEntity(type, cursor.getLong(columnsOrdered.indexOf(idName)));
                    if (entity == null) {
                        entity = (T) entityConstructor.newInstance();
                    }

                    entity.loadFromCursor(cursor);
                    entities.add((T) entity);
                }
                while (cursor.moveToNext());
            }

        } catch (NoSuchMethodException e) {
            throw new RuntimeException(
                    "Your model " + type.getName() + " does not define a default " +
                            "constructor. The default constructor is required for " +
                            "now in ActiveAndroid models, as the process to " +
                            "populate the ORM model is : " +
                            "1. instantiate default model " +
                            "2. populate fields"
            );
        } catch (Exception e) {
            Log.e("Failed to process cursor.", e);
        }

        return entities;
    }

    private static int processIntCursor(final Cursor cursor) {
        if (cursor.moveToFirst()) {
            return cursor.getInt(0);
        }
        return 0;
    }

    public static List lexSqlScript(String sqlScript) {
        ArrayList sl = new ArrayList();
        boolean inString = false, quoteNext = false;
        StringBuilder b = new StringBuilder(100);

        for (int i = 0; i < sqlScript.length(); i++) {
            char c = sqlScript.charAt(i);

            if (c == ';' && !inString && !quoteNext) {
                sl.add(b.toString());
                b = new StringBuilder(100);
                inString = false;
                quoteNext = false;
                continue;
            }

            if (c == '\'' && !quoteNext) {
                inString = !inString;
            }

            quoteNext = c == '\\' && !quoteNext;

            b.append(c);
        }

        if (b.length() > 0) {
            sl.add(b.toString());
        }

        return sl;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy