Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added initial support for Composite Foreign Keys and Indexes #19

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ preprocessor/target
common/target
build
*.keystore
*.~*
projectBackupFiles
61 changes: 54 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ annotations and CRUD classes expose an expressive api for executing SQLite queri
preprocessors on Android.*
```groovy
dependencies {
apt 'com.memtrip.sqlking:preprocessor:1.1.5'
compile 'com.memtrip.sqlking:client:1.1.5'
apt 'com.memtrip.sqlking:preprocessor:1.1.6'
compile 'com.memtrip.sqlking:client:1.1.6'
}
```

Expand Down Expand Up @@ -230,12 +230,27 @@ User[] users = Select.getBuilder()
####Joins####
Joins can be performed using the `InnerJoin`, `LeftOutJoin`, `CrossInnerJoin`, `NaturalInnerJoin`, `NaturalLeftOuterJoin` classes.
The target table for the join must be defined as an @Column, the object will be populated with any join results.
Single Column Constraints and Foreignk Keys can also be defined.

```java
@Table
public class Comment {
@Column(index = true) int id;
@Column int userId;
@Column([@ForeignKey // Single column ForeignKey (not required for joins, but will be enforced by SQLite DBMS)
(
foreignTablename="User",
localColumnNames ={"user_id"},
foreignColumnNames = {"id"},
[update/deleteRule=RIRule.Cascade|...][,...]
)
int userId;]
@Constraints = {@Constraint // Single column Constraints, see SQLite documentation fot `table_constraint`
(
constraintName = "constraintName", // will be created as "table_name_constraint_name_constraint". the constraint name must be unique within the constraints for the table
expression = "some expression eg, PRIMARY|FOREIGN KEY, UNIQUE, CHECK"
[,@onConflict (ConflictAction.ROLLBACK|ABORT|FAIL|etc)] // optional ON CONFLICT clause, if required and appropriate
)[, ...] // optional additional @Constraint statements

@Column User user; // The target table for a potential join

public int getId() {
Expand Down Expand Up @@ -265,7 +280,7 @@ public class Comment {

@Table
public class User {
@Column(index = true) int id;
@Column(index = true) int id; // Single column index (short form syntax)

public int getId() {
return id;
Expand All @@ -282,8 +297,9 @@ Comment[] comments = Select.getBuilder()

User user = comments[0].getUser(); // The nested User object is populated by the join
```

####Primary Key####
An auto incrementing primary key can be defined using:
An auto incrementing primary key can be defined on an `int` or `long` column using:

```java
@Table
Expand All @@ -300,13 +316,44 @@ public class Data {
}
```

####Table Constraints, Composite Indexes and Foreign Keys####
Multiple Composite Indexes and Foreign Keys can be defined for the table, and will be created automatically when the table is created:

```java
@Table (
foreignkeys = {@ForeignKey ( // (not required for joins, but will be enforced by SQLite DBMS)
foreignTableName = "xxxx", // will be created as "fk_localTableName_foreignTableName_n", _n increments for multiple links to the same foreign table
localColumnNames = {"localcolumn"[, ...] // Multiple columns possible
},
foreignColumnNames = {"foreignColumn"[, ...] // Multiple columns possible
}
)[, ...] // optional additional @ForeignKey statements
},
indexes = {@Index (
indexName = "index_name", // will be created as "table_name_index_name_index". index_name must be unique within the indexes on the parent table
columns = {@IndexColumn (column = "column1"
[,sortOrder = SortOrder.ASC|SortOrder.DESC] // optional column sort order (default is SortOrder.ASC)
)[, ...] // optional additional @IndexColumn statements
}
)[, ...] // optional additional @Index statements
},
constraints = {@Constraint (
constraintName = "constraintName", // will be created as "table_name_constraint_name_constraint". the constraint name must be unique within the constraints for the table
expression = "some expression eg, PRIMARY|FOREIGN KEY, UNIQUE, CHECK" // see SQLite documentation fot `table_constraint`
[,@onConflict (ConflictAction.ROLLBACK|ABORT|FAIL|etc)] // as appropriate, if required
)[, ...] // optional additional @Constraint statements
}
)
```

####Tests####
The `tests/java/com/memtrip/sqlking` package contains a full set of unit and integration tests. The
tests can be used as a good reference on how to structure queries.

####TODO####
- Validate that object relationships defined by @Column are annotated with @Table
- Validate that auto_increment columns must be int or long
- @Table annotation should support foreign_key functionality
- @NotNull annotation and handle this validation in the software layer
- Composite Foreign Key Constraints
- Add table Alias support for Queries, Foreign keys and Joins
- Allow for join creation based on Foreign key Annotations (including aliases)
- Add support for database version upgrades scripts, so that exisiting data is retained
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ buildscript {
}
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
classpath 'com.android.tools.build:gradle:1.5.0'
classpath 'com.android.tools.build:gradle:2.2.0'
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.6'
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3'
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1'
}
}
8 changes: 4 additions & 4 deletions client/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ ext {
siteUrl = 'https://github.com/memtrip/SQLKing'
gitUrl = 'https://github.com/memtrip/SQLKing.git'

libraryVersion = '1.1.5'
libraryVersion = '1.1.7'

developerId = 'samkirton'
developerName = 'Samuel Kirton'
Expand All @@ -44,7 +44,7 @@ android {
minSdkVersion 11
targetSdkVersion 24
versionCode 7
versionName "1.1.1"
versionName "1.1.7"

testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
Expand Down Expand Up @@ -137,11 +137,11 @@ install {
}

dependencies {
compile 'com.memtrip.sqlking:common:1.1.5'
compile 'com.memtrip.sqlking:common:1.1.7'
compile 'io.reactivex:rxjava:1.1.1'
compile 'io.reactivex:rxandroid:1.1.0'

androidTestApt 'com.memtrip.sqlking:preprocessor:1.1.5'
androidTestApt 'com.memtrip.sqlking:preprocessor:1.1.7'

androidTestCompile(
'com.android.support.test:runner:0.3',
Expand Down
17 changes: 15 additions & 2 deletions client/src/main/java/com/memtrip/sqlking/database/SQLInit.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,29 @@ public static SQLProvider createDatabase(String name,

String[] schemaArray = new String[modelCount];
String[] tableNameArray = new String[modelCount];

String[] createIndexArray = new String[modelCount];
List<String> indexNameArray = new ArrayList<>();

String[] createTriggerArray = new String[modelCount];;
List<String> triggerNameArray = new ArrayList<>();

for (int i = 0; i < modelClassDef.length; i++) {
SQLQuery sqlQuery = resolver.getSQLQuery(modelClassDef[i]);
schemaArray[i] = sqlQuery.getTableInsertQuery();

schemaArray[i] = sqlQuery.getTableCreateQuery();
tableNameArray[i] = sqlQuery.getTableName();
createIndexArray[i] = sqlQuery.getCreateIndexQuery();
createIndexArray[i] = sqlQuery.getCreateIndexesQuery();
createTriggerArray[i] = sqlQuery.getCreateTriggersQuery();

for (String indexName : sqlQuery.getIndexNames()) {
indexNameArray.add(indexName);
}

for (String triggerName : sqlQuery.getTriggerNames()) {
triggerNameArray.add(triggerName);
}

}

SQLOpen sqlOpen = new SQLOpen(
Expand All @@ -60,6 +71,8 @@ public static SQLProvider createDatabase(String name,
tableNameArray,
createIndexArray,
indexNameArray,
createTriggerArray,
triggerNameArray,
context
);

Expand Down
37 changes: 26 additions & 11 deletions client/src/main/java/com/memtrip/sqlking/database/SQLOpen.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,30 +23,39 @@

/**
* @author Samuel Kirton [[email protected]]
* @author Adrian Velcich [[email protected]]
*
*/
public class SQLOpen extends SQLiteOpenHelper {
private SQLiteDatabase mDatabase;
private String[] mSchemaArray;
private String[] mTableNameArray;
private String[] mCreateIndexQuery;
private List<String> mIndexNames;

private String[] mCreateIndexStatements;
private List<String> mIndexNames;
private String[] mCreateTriggerStatements;
private List<String> mTriggerNames;

protected SQLiteDatabase getDatabase() {
return mDatabase;
}

protected SQLOpen(String name, int version, String[] schemaArray,
String[] tableNameArray,
String[] indexQuery,
String[] createIndexStatements,
List<String> indexNames,
String[] createTriggerStatements,
List<String> triggerNames,
Context context) {

super(context, name, null, version);

mSchemaArray = schemaArray;
mTableNameArray = tableNameArray;
mCreateIndexQuery = indexQuery;
mIndexNames = indexNames;
mCreateIndexStatements = createIndexStatements;
mIndexNames = indexNames;
mCreateTriggerStatements = createTriggerStatements;
mTriggerNames = triggerNames;

mDatabase = getWritableDatabase();
}

Expand All @@ -57,11 +66,17 @@ public void onCreate(SQLiteDatabase db) {
db.execSQL(schema);
}

for (String createIndex : mCreateIndexQuery) {
if (createIndex != null) {
db.execSQL(createIndex);
}
}
for (String createIndex : mCreateIndexStatements) {
if (createIndex != null) {
db.execSQL(createIndex);
}
}

for (String createTrigger : mCreateTriggerStatements) {
if (createTrigger != null) {
db.execSQL(createTrigger);
}
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public void testMultipleInsert() {
};

// exercise
Insert.getBuilder().values(users).execute(getSQLProvider());
Insert.getBuilder().values((Object[])users).execute(getSQLProvider());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the cast to Object[] required?


// verify
User angieUser = Select.getBuilder()
Expand Down Expand Up @@ -148,7 +148,7 @@ public void testMoreThan500RowInsert() {
);
}

Insert.getBuilder().values(users).execute(getSQLProvider());
Insert.getBuilder().values((Object[])users).execute(getSQLProvider());

User[] usersInserted = Select.getBuilder().execute(User.class, getSQLProvider());

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* Copyright 2013-present memtrip LTD.
*
* 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.
*/
package com.memtrip.sqlking.integration;

import android.database.Cursor;

import com.memtrip.sqlking.operation.function.Raw;

import org.junit.Before;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;

/**
* @author Adrian Velcich [[email protected]]
*/
public class ForeignKeyTest extends IntegrationTest
{
@Before
public void setUp()
{
super.setUp();

getSetupData().tearDownTestData(getSQLProvider());
getSetupData().setupTestData(getSQLProvider());

getSetupLog().tearDownTestLogs(getSQLProvider());
getSetupLog().setupTestLogs(getSQLProvider());
}

@Test
public void testUserForeignKeyIsCreated()
{
Cursor cursor = Raw.getBuilder()
.query("PRAGMA foreign_key_list('User');")
.execute(getSQLProvider());

List<String> foreignKeys = getForeignKeys(cursor);

assertEquals(1, foreignKeys.size());
}

private List<String> getForeignKeys(Cursor cursor)
{
List<String> foreignKeys = new ArrayList<>();

try {
while (cursor.moveToNext())
{
int foreignKey = cursor.getColumnIndex("name");

if (foreignKey != -1)
{
String foreignKeyName = cursor.getString(foreignKey);
foreignKeys.add(foreignKeyName);
}
}
}
finally
{
cursor.close();
}

return foreignKeys;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void setUp() {
@Test
public void testPostIndexesAreCreated() {
Cursor cursor = Raw.getBuilder()
.query("PRAGMA INDEX_LIST('Post');")
.query("PRAGMA index_list('Post');")
.execute(getSQLProvider());

List<String> indexes = getIndexes(cursor);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
@Table(
foreignKeys = {
@ForeignKey(
targetTable = "User",
targetColumn = "id",
localColumn = "userId"
foreignTableName = "User",
localColumnNames = {"id"},
foreignColumnNames = {"userId"}
)
}
)
Expand Down
Loading