I'm using sqlflite with flutter app and I have this methode that initialize the database:
initDB() async{
String dbPath = await getDatabasesPath();
String path = join(dbPath, 'keemot.db');
var ourDB = await openDatabase(
path,
version: 1,
onCreate: _onCreat,
);
return ourDB;
}
and this is _onCreate method:
void _onCreat(Database db, int version) async{
await db.execute(
'CREATE TABLE $_table ('
'$_columnId INTEGER PRIMARY KEY,'
'$_columnTite TEXT,'
'$_columnDate TEXT,'
'$_columnTime TEXT,'
'$_columnMonth INTEGER,'
'$_columnDay INTEGER,'
'$_columnReiteration INTEGER,'
'$_columnReiterationTarget TEXT,'
'$_columnNotification INTEGER,'
'$_columnNotificationTarget TEXT'
')'
);
}
and I wanted to add another table so I did the following:
void _onCreat(Database db, int version) async{
print ('creating ... version => $version');
await db.execute(
'CREATE TABLE $_table ('
'$_columnId INTEGER PRIMARY KEY,'
'$_columnTite TEXT,'
'$_columnDate TEXT,'
'$_columnTime TEXT,'
'$_columnMonth INTEGER,'
'$_columnDay INTEGER,'
'$_columnReiteration INTEGER,'
'$_columnReiterationTarget TEXT,'
'$_columnNotification INTEGER,'
'$_columnNotificationTarget TEXT'
');'
'CREATE TABLE $_settingsTable (' // I added the table to this method like so
'$_settingsColumnLang VARCHAR (3)'
');'
);
}
But I'm getting this exception:
DatabaseException(no such table: settings)
I have updated the version in openDatabase() method, I thought it will recall the _onCreate() method, however still getting the same exception.
What should I do update the database structure ?
I think you should call db.execute two times for creating two seperate tables.
Also for cases like this I would recommend also providing the onUpgrade callback. You could do something like this:
var ourDB = await openDatabase(
path,
version: 3,
onCreate: _onCreat,
onUpgrade: _onUpgrade
);
And your upgrade method might look like this to drop the old table and call your onCreate method again:
_onUpgrade(Database db, int oldVersion, int newVersion) {
await db.execute("DROP TABLE IF EXISTS $_table");
_onCreat(db, newVersion);
}
Related
I'm trying to do a Typeorm query depending on an element of an object but I have an issue with the forEach and the async function.
I have read a lot of similar issues but no one worked for me as expected.
Here is my code snippet with the await not allowed in a forEach:
public async runBudgetLimit(): Promise<void> {
const contracts: ContractEntity[] = await this.contractService.getAllProjects();
contracts.forEach((contract) => {
const project: UserEntity = await this.userService.findOne(contract.milestoneId);
const date: string = Time.moment().format('YYYY-MM-DD');
const budgetUsed = this.trackService.getBillingByProject(project.contractId, date, '1000', '1000', true);
});
}
Here is the async Typeorm query:
async findOne(id: string): Promise<UserEntity | undefined> {
return this.userRepository.findOne(id);
}
I don't know what is the best solution to solve this issue, I don't think that the for loop is a good solution but I'm open to all solutions.
You can just use for-of loop like this:
for (const contract of contracts) {
const project: UserEntity = await this.userService.findOne(contract.milestoneId);
const date: string = Time.moment().format('YYYY-MM-DD');
const budgetUsed = this.trackService.getBillingByProject(project.contractId, date, '1000', '1000', true);
}
Or with slight optimisations - check this answer for differences between for-of and Promise.all approaches:
await Promise.all(contracts.map(async contract => {
const project: UserEntity = await this.userService.findOne(contract.milestoneId);
const date: string = Time.moment().format('YYYY-MM-DD');
const budgetUsed = this.trackService.getBillingByProject(project.contractId, date, '1000', '1000', true);
}));
Keep in mind that these solutions are not optimal in some cases, because by getting UserEntity for every contract there is an additional query to the database AKA N+1 query problem. To fix this you can load all of the users along with the contracts by using the relations array.
I am not sure why are you getting contractId from the project, isn't it available on the contract object?
Also if the response from getBillingByProject is a promise you should put await in front
I need to add a new column name id INTEGER AUTOINCREMENT and a new table for the current database for my existing table. How to use 'onUpgrade'? Is it need to change the version number?
initDb() async {
io.Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "HelperDatabase.db");
var theDb = await openDatabase(path, version: 1, onCreate: _onCreate, onUpgrade: _onUpgrade);
return theDb;
}
How to use _onUpgrade
void _onUpgrade(Database db, int oldVersion, int newVersion)async{
}
Is it need to add column also?
void _onCreate(Database db, int version) async {
await db.execute(
"""CREATE TABLE AssetAssemblyTable(e INTEGER, a INTEGER, c INTEGER)""");
To update your DB from old version, you should change version to 2.
You should change onCreate and onUpdate like below.
// This is called for new users who have no old db
void _onCreate(Database db, int version) async {
// if `AssetAssemblyTable` has a new column in version 2, add the column here.
await db.execute(
"""CREATE TABLE AssetAssemblyTable(e INTEGER, a INTEGER, c INTEGER)""");
)
await db.execute("CREATE TABLE NewTable...") // create new Table
}
// This is called for existing users who have old db(version 1)
void _onUpgrade(Database db, int oldVersion, int newVersion)async{
// In this case, oldVersion is 1, newVersion is 2
if (oldVersion == 1) {
await db.execute("ALTER TABLE AssetAssemblyTable...") // add new column to existing table.
await db.execute("CREATE TABLE NewTable...") // create new Table
}
}
more example is below
https://github.com/tekartik/sqflite/blob/master/sqflite/doc/migration_example.md
Here what I've done :
class SqliteDB {
static final SqliteDB _instance = new SqliteDB.internal();
factory SqliteDB() => _instance;
static Database? _db;
Future<Database?> get db async {
if (_db != null) {
return _db;
}
_db = await initDb();
return _db;
}
SqliteDB.internal();
/// Initialize DB
initDb() async {
io.Directory documentDirectory = await
getApplicationDocumentsDirectory();
String path = join(documentDirectory.path, "RadiosStations.db");
var taskDb = await openDatabase(
//open the database or create a database if there isn't any path,
version: 2, onCreate: (Database db, int version) async {
await db.execute(
"""CREATE TABLE AssetAssemblyTable(e INTEGER, a INTEGER, c
INTEGER)""");
)
await db.execute("CREATE TABLE NewTable...") // create new Table;
},
onUpgrade: (Database db, int oldVersion, int newVersion)async{
// In this case, oldVersion is 1, newVersion is 2
if (oldVersion == 1) {
await db.execute(
"""CREATE TABLE AssetAssemblyTable(e INTEGER, a INTEGER, c
INTEGER)""");
)
await db.execute("CREATE TABLE NewTable...") // create new Table;
}}
);
return taskDb;}
How do you insert data into a database in Flutter using the SQFlite plugin?
There are a number of problem solving questions out there but none that I could find to add a canonical answer to. My answer is below.
Add the dependencies
Open pubspec.yaml and in the dependency section add the following lines:
sqflite: ^1.0.0
path_provider: ^0.4.1
The sqflite is the SQFlite plugin of course and the path_provider will help us get the user directory on Android and iPhone.
Make a database helper class
I'm keeping a global reference to the database in a singleton class. This will prevent concurrency issues and data leaks (that's what I hear, but tell me if I'm wrong). You can also add helper methods (like insert) in here for accessing the database.
Create a new file called database_helper.dart and paste in the following code:
import 'dart:io' show Directory;
import 'package:path/path.dart' show join;
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart' show getApplicationDocumentsDirectory;
class DatabaseHelper {
static final _databaseName = "MyDatabase.db";
static final _databaseVersion = 1;
static final table = 'my_table';
static final columnId = '_id';
static final columnName = 'name';
static final columnAge = 'age';
// make this a singleton class
DatabaseHelper._privateConstructor();
static final DatabaseHelper instance = DatabaseHelper._privateConstructor();
// only have a single app-wide reference to the database
static Database _database;
Future<Database> get database async {
if (_database != null) return _database;
// lazily instantiate the db the first time it is accessed
_database = await _initDatabase();
return _database;
}
// this opens the database (and creates it if it doesn't exist)
_initDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, _databaseName);
return await openDatabase(path,
version: _databaseVersion,
onCreate: _onCreate);
}
// SQL code to create the database table
Future _onCreate(Database db, int version) async {
await db.execute('''
CREATE TABLE $table (
$columnId INTEGER PRIMARY KEY,
$columnName TEXT NOT NULL,
$columnAge INTEGER NOT NULL
)
''');
}
}
Insert data
We'll use an async method to do our insert:
_insert() async {
// get a reference to the database
// because this is an expensive operation we use async and await
Database db = await DatabaseHelper.instance.database;
// row to insert
Map<String, dynamic> row = {
DatabaseHelper.columnName : 'Bob',
DatabaseHelper.columnAge : 23
};
// do the insert and get the id of the inserted row
int id = await db.insert(DatabaseHelper.table, row);
// show the results: print all rows in the db
print(await db.query(DatabaseHelper.table));
}
Notes
You will have to import the DatabaseHelper class and sqflite if you are in another file (like main.dart).
The SQFlite plugin uses a Map<String, dynamic> to map the column names to the data in each row.
We didn't specify the id. SQLite auto increments it for us.
Raw insert
SQFlite also supports doing a raw insert. This means that you can use a SQL string. Lets insert the same row again using rawInsert().
db.rawInsert('INSERT INTO my_table(name, age) VALUES("Bob", 23)');
Of course, we wouldn't want to hard code those values into the SQL string, but we also wouldn't want to use interpelation like this:
String name = 'Bob';
int age = 23;
db.rawInsert('INSERT INTO my_table(name, age) VALUES($name, $age)'); // Dangerous!
That would open us up to SQL injection attacks. Instead we can use data binding like this:
db.rawInsert('INSERT INTO my_table(name, age) VALUES(?, ?)', [name, age]);
The [name, age] are filled in for the question mark placeholders in (?, ?). The table and column names are safer to use interpelation for, so we could do this finally:
String name = 'Bob';
int age = 23;
db.rawInsert(
'INSERT INTO ${DatabaseHelper.table}'
'(${DatabaseHelper.columnName}, ${DatabaseHelper.columnAge}) '
'VALUES(?, ?)', [name, age]);
Supplemental code
For your copy-and-paste convenience, here is the layout code for main.dart:
import 'package:flutter/material.dart';
import 'package:flutter_db_operations/database_helper.dart';
import 'package:sqflite/sqflite.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SQFlite Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('sqflite'),
),
body: RaisedButton(
child: Text('insert', style: TextStyle(fontSize: 20),),
onPressed: () {_insert();},
),
);
}
_insert() async {
// get a reference to the database
// because this is an expensive operation we use async and await
Database db = await DatabaseHelper.instance.database;
// row to insert
Map<String, dynamic> row = {
DatabaseHelper.columnName : 'Bob',
DatabaseHelper.columnAge : 23
};
// do the insert and get the id of the inserted row
int id = await db.insert(DatabaseHelper.table, row);
// raw insert
//
// String name = 'Bob';
// int age = 23;
// int id = await db.rawInsert(
// 'INSERT INTO ${DatabaseHelper.table}'
// '(${DatabaseHelper.columnName}, ${DatabaseHelper.columnAge}) '
// 'VALUES(?, ?)', [name, age]);
print(await db.query(DatabaseHelper.table));
}
}
Going on
This post is a development from my previous post: Simple SQFlite database example in Flutter. See that post for other SQL operations and advice.
get in pubspec.yaml
add
dependency:
sqflite: any,
path_provider: any,
intl: ^0.15.7
then click package get and package upgrade
I have a simple flow which aim is to write two lines in one BigQuery Table.
I use a DynamicDestinations because after that I will write on mutliple Table, on that example it's the same table...
The problem is that I only have 1 line in my BigQuery table at the end.
It stacktrace I see the following error on the second insert
"
status: {
code: 6
message: "Already Exists: Job sampleprojet3:b9912b9b05794aec8f4292b2ae493612_eeb0082ade6f4a58a14753d1cc92ddbc_00001-0"
}
"
What does it means ?
Is it related to this limitation ?
https://github.com/GoogleCloudPlatform/DataflowJavaSDK/issues/550
How can I do the job ?
I use BeamSDK 2.0.0, I have try with 2.1.0 (same result)
The way I launch :
mvn compile exec:java -Dexec.mainClass=fr.gireve.dataflow.LogsFlowBug -Dexec.args="--runner=DataflowRunner --inputDir=gs://sampleprojet3.appspot.com/ --project=sampleprojet3 --stagingLocation=gs://dataflow-sampleprojet3/tmp" -Pdataflow-runner
Pipeline p = Pipeline.create(options);
final List<String> tableNameTableValue = Arrays.asList("table1:value1", "table1:value2", "table2:value1", "table2:value2");
p.apply(Create.of(tableNameTableValue)).setCoder(StringUtf8Coder.of())
.apply(BigQueryIO.<String>write()
.withCreateDisposition(BigQueryIO.Write.CreateDisposition.CREATE_IF_NEEDED)
.withWriteDisposition(BigQueryIO.Write.WriteDisposition.WRITE_APPEND)
.to(new DynamicDestinations<String, KV<String, String>>() {
#Override
public KV<String, String> getDestination(ValueInSingleWindow<String> element) {
final String[] split = element.getValue().split(":");
return KV.of(split[0], split[1]) ;
}
#Override
public Coder<KV<String, String>> getDestinationCoder() {
return KvCoder.of(StringUtf8Coder.of(), StringUtf8Coder.of());
}
#Override
public TableDestination getTable(KV<String, String> row) {
String tableName = row.getKey();
String tableSpec = "sampleprojet3:testLoadJSON." + tableName;
return new TableDestination(tableSpec, "Table " + tableName);
}
#Override
public TableSchema getSchema(KV<String, String> row) {
List<TableFieldSchema> fields = new ArrayList<>();
fields.add(new TableFieldSchema().setName("myColumn").setType("STRING"));
TableSchema ts = new TableSchema();
ts.setFields(fields);
return ts;
}
})
.withFormatFunction(new SerializableFunction<String, TableRow>() {
public TableRow apply(String row) {
TableRow tr = new TableRow();
tr.set("myColumn", row);
return tr;
}
}));
p.run().waitUntilFinish();
Thanks
DynamicDestinations associates each element with a destination - i.e. where the element should go. Elements are routed to BigQuery tables according to their destinations: 1 destination = 1 BigQuery table with a schema: the destination should include just enough information to produce a TableDestination and a schema. Elements with the same destination go to the same table, elements with different destinations go to different tables.
Your code snippet uses DynamicDestinations with a destination type that contains both the element and the table, which is unnecessary, and of course, violates the constraint above: elements with a different destination end up going to the same table: e.g. KV("table1", "value1") and KV("table1", "value2") are different destinations but your getTable maps them to the same table table1.
You need to remove the element from your destination type. That will also lead to simpler code. As a side note, I think you don't need to override getDestinationCoder() - it can be inferred automatically.
Try this:
.to(new DynamicDestinations<String, String>() {
#Override
public String getDestination(ValueInSingleWindow<String> element) {
return element.getValue().split(":")[0];
}
#Override
public TableDestination getTable(String tableName) {
return new TableDestination(
"sampleprojet3:testLoadJSON." + tableName, "Table " + tableName);
}
#Override
public TableSchema getSchema(String tableName) {
List<TableFieldSchema> fields = Arrays.asList(
new TableFieldSchema().setName("myColumn").setType("STRING"));
return new TableSchema().setFields(fields);
}
})
I want to put a generic POJO into ContentValues and unmarshall it within the ContentProvider.
I've been wracking my tiny brain re: Parcelables, ContentValues, and inserting into SQLite
Regarding:
http://njzk2.wordpress.com/2013/05/31/map-to-contentvalues-abusing-parcelable/
How to write a common code for inserting data in android's Sqlite
I've been trying to insert a android.location.Location into SQLite via ContentProvider:
Location loc = mLocationClient.getLastLocation();
myParcel = android.os.Parcel.obtain();
loc.writeToParcel(myParcel, 0);
ContentValues values = ContentValues.CREATOR.createFromParcel(myParcel );
to populate values w/ parcel.
Question 1)
Here is my ContentProvider.insert method:
#Override
public Uri insert( final Uri uri, final ContentValues values ){
SQLiteDatabase db = Mydatabase.getWritableDatabase();
//db.insert() doesn’t unmarshal the values??
db.insert( myTABLE_NAME, “”, values);
Uri result = null;
db.close();
return result;
}
this fails because the db.insert() doesn’t unmarshal the values (i believe)
Error inserting android.database.sqlite.SQLiteException: INSERT INTO myTABLE_NAME() VALUES (NULL)
Question 2)
Is there some way I can unmarshal values first and then marshal it back into another ContentValues variable? maybe w/ getKey()???
This works:
HashMap hm = new HashMap();
Location loc = mLocationClient.getLastLocation();
hm.put("LOCATIONS", loc);
android.os.Parcel myParcel = android.os.Parcel.obtain();
myParcel.writeMap(hm);
myParcel.setDataPosition(0);
ContentValues values = ContentValues.CREATOR.createFromParcel(myParcel);
getContentResolver().insert(MyUri, values);
and then
#Override
public Uri insert( final Uri uri, final ContentValues oldvalues ){
SQLiteDatabase db = GAELdatabase.getWritableDatabase();
Uri result = null;
Location loc = (Location)oldvalues.get("LOCATIONS");
ContentValues values = new ContentValues();
values.put("ALTITUDE", loc.getAltitude());//meters above sea level
values.put("LATITUDE", loc.getLatitude());
values.put("LONGITUDE", loc.getLongitude());
long rowID = db.insert( "MyTABLE_NAME", "", values);
db.close();
return result;
}