如何使用精灵编程语言将信息插入Sqlite数据库?

时间:2015-10-11 00:20:05

标签: python sqlite vala genie

这个问题脱离了前一个问题here,其中创建了一个数据库。但是,当涉及向该数据集添加信息时,我可以手动添加信息或通过编程方式进行。后者是我选择的教学理由。

我在python中尝试做的相当于:

for x in cursor.execute(sql):
    lastid = x[0]
    # Insert data into the instructions table
    sql = 'INSERT INTO Instructions (recipeID,instructions) VALUES( %s,"Brown hamburger. Stir in all other ingredients. Bring to a boil. Stir. Lower to simmer. Cover and cook for 20 minutes or until all liquid is absorbed.")' % lastid
    cursor.execute(sql)

我的方式是:

//Insert the rest of instructions
var last_id = db.last_insert_rowid()
for var x in last_id
    query = """INSERT INTO Instructions (recipeID,instructions) VALUES(
         %s,
         "Brown hamburger. Stir in all other ingredients. Bring to a boil. Stir. Lower to simmer. Cover and cook for 20 minutes or until all liquid is absorbed."
         ), x
            """

但是,似乎last_id是一个int64类型,不能是迭代器,因为我得到的错误:

  

valac --pkg sqlite3 cookcreate.gs cookcreate.gs:55.18-55.24:错误:   int64' does not have an迭代'方法       对于last_id中的var x                    ^^^^^^^编译失败:1个错误,0个警告

如何用Genie中的代码解决这个问题?我应该将它转换为另一种类型,它接受被用作迭代器吗?此外,语法(%s), x是否正确?

由于

2 个答案:

答案 0 :(得分:1)

问题的关键在于如何获取最后一个插入值(Recipes表的主键)并将其放入下一个语句中。

要使插入完全安全(证明SQL injection),您应该使用prepared statement

我还添加了更多的错误检查。

[indent=4]

def check_ok (db : Sqlite.Database, ec : int)
    if (ec != Sqlite.OK)
        stderr.printf ("Error: %d: %s\n", db.errcode (), db.errmsg ())
        Process.exit (-1)

def checked_exec (db : Sqlite.Database, sql : string)
    check_ok (db, db.exec (sql))

init
    // Opening/creating database. Database name is cookbook.db3
    db : Sqlite.Database? = null
    if (Sqlite.Database.open ("cookbook.db3", out db) != Sqlite.OK)
        stderr.printf ("Error: %d: %s\n", db.errcode (), db.errmsg ())
        Process.exit (-1)
    checked_exec (db, "CREATE TABLE Recipes (pkiD INTEGER PRIMARY KEY, name TEXT, servings TEXT, source TEXT)")
    checked_exec (db, "CREATE TABLE Instructions (pkID INTEGER PRIMARY KEY, instructions TEXT, recipeID NUMERIC)")
    checked_exec (db, "CREATE TABLE Ingredients (pkID INTEGER PRIMARY KEY, ingredients TEXT, recipeID NUMERIC)")

    // Insert data into Recipe table
    checked_exec (db, """INSERT INTO Recipes (name, servings, source) VALUES ("Spanish Rice", 4, "Greg")""")
    lastid : int64 = db.last_insert_rowid ()

    // Insert data into Inctructions table
    instr_sql : string = """INSERT INTO Instructions (recipeID, instructions) VALUES($recipeID, "Brown hamburger. Stir in all other ingredients. Bring to a boil. Stir. Lower to simmer. Cover and cook for 20 minutes or until all liquid is absorbed.")"""
    instr_stmt : Sqlite.Statement = null
    check_ok (db, db.prepare_v2 (instr_sql, instr_sql.length, out instr_stmt))
    param_position : int = instr_stmt.bind_parameter_index ("$recipeID")
    assert (param_position > 0)
    check_ok (db, instr_stmt.bind_int64 (param_position, lastid))
    // Warning: Statment.step uses a different return value mechanism
    //          check_ok can't be used here
    if (instr_stmt.step () != Sqlite.DONE)
        stderr.printf ("Error: %d: %s\n", db.errcode (), db.errmsg ())
        Process.exit (-1)
PS:如果我要写一个真正的程序,我可能首先用错误域编写更高级别的SQLite抽象。使用这种抽象,代码会短得多。

答案 1 :(得分:1)

您似乎遇到的问题是使用last_insert_rowid()来创建外键。 last_insert_rowid()是单个值,而不是值集合。因此,无需在for循环中循环它。

以下示例使用预准备语句将值插入两个表中。第一个表包含用户名,第二个表包含用户表的外键和随机生成的引用ID。

您正在查看的问题区域是数据加载。因此,该程序可以构成利用Genie性能的数据加载程序的基础。例如,如果你想在加载之前以某种方式整理数据,那么Genie可能对此有好处。稍后有关性能的更多细节。

[indent=4]
uses Sqlite

exception DatabaseError
    FAILED_TO_CREATE_DATABASE
    FAILED_TO_CREATE_TABLES
    FAILED_TO_LOAD_DATA

init
    try
        database:Database = create_database( "example.sqlite" )
        create_tables( database )
        load_data( database )
    except error:DatabaseError
        print error.message
        Process.exit( -1 )

def load_data( db:Database ) raises DatabaseError
    user_insert_stmnt:Statement = prepare_user_insert_stmnt( db )
    posts_insert_stmnt:Statement = prepare_posts_insert_stmnt( db )

    var data = new DataGenerator()
    user_id:int64 = 0
    db.exec( "BEGIN TRANSACTION" )
    while data.read()
        user_insert_stmnt.bind_text( 
                    user_insert_stmnt.bind_parameter_index( "@name" ), 
                    data.user_name
                    )
        user_insert_stmnt.step()
        user_insert_stmnt.reset()
        user_id = db.last_insert_rowid()
        for var reference_id in data.reference_ids
            posts_insert_stmnt.bind_int64( 
                        posts_insert_stmnt.bind_parameter_index( "@user_id" ),
                        user_id
                        )
            posts_insert_stmnt.bind_int64( 
                        posts_insert_stmnt.bind_parameter_index( "@reference_id" ),
                        reference_id
                        )
            posts_insert_stmnt.step()
            posts_insert_stmnt.reset()
    db.exec( "END TRANSACTION" )

def prepare_user_insert_stmnt( db:Database ):Statement
    statement:Statement
    db.prepare_v2( """
insert into users( 
    name
    )
    values( @name )
""", -1, out statement )
    return statement

def prepare_posts_insert_stmnt( db:Database ):Statement
    statement:Statement
    db.prepare_v2( """
insert into posts( 
    user_id,
    reference_id
    )
    values( @user_id, @reference_id )
""", -1, out statement )
    return statement

class DataGenerator
    user_name:string = ""
    reference_ids:array of uint = new array of uint[ 2 ]

    _iteration:int = 0
    _max_iterations:int = 10000

    def read():bool
        user_name = "User%06d".printf( _iteration )
        _iteration++

        for a:int = 0 to (reference_ids.length -1)
            reference_ids[ a ] = Random.next_int()

        more:bool = true
        if _iteration > _max_iterations
            more = false
        return more

def create_database( db_name:string ):Database raises DatabaseError
    db:Database
    result:int = Database.open( db_name, out db )
    if result != OK
        raise new DatabaseError.FAILED_TO_CREATE_DATABASE( 
                                 "Can't create %s SQLite error %d, \"%s\"", 
                                 db_name,
                                 db.errcode(),
                                 db.errmsg()
                                 )
    return db

def create_tables( db:Database ) raises DatabaseError
    sql:string = """
create table users ( id integer primary key,
                    name varchar not null
                    );
create table posts ( id integer primary key,
                    user_id integer not null,
                    reference_id integer not null
                    );
"""
    if db.exec( sql ) != OK
        raise new DatabaseError.FAILED_TO_CREATE_TABLES( 
                                 "Can't create tables. SQLite error %d, \"%s\"", 
                                 db.errcode(),
                                 db.errmsg()
                                 )

需要注意的一些要点:

  • 创建数据库和表的函数位于程序的末尾,因为它们仅用于示例工作
  • 使用try...except允许程序在try块结束时取消引用任何对象时发生错误时停止,然后except块可以安全地使用Process.exit( -1 ) 。通过返回-1,程序可以向任何调用脚本发出信号,表明加载失败
  • 程序已被拆分为单独的函数和类,请注意数据库连接作为参数传递给每个函数,这是编程中封装的原则
  • DataGenerator类还提供了封装示例,它跟踪已生成的示例数,然后在超出_max_iterations限制时停止
  • DataGenerator类可以很容易地用于从文件中读取。希望你能开始看到Genie和任何其他面向对象编程语言一样,如何帮助模块化你的代码
  • 每个用户都有两个帖子,因此程序必须存储last_insert_rowid(),否则当last_insert_rowid()更改插入的第一个帖子的ID时,数据将会损坏
  • DataGenerator创建了一万个示例,这些示例在我的计算机上加载了大约四分之一秒。注释掉BEGIN TRANSACTIONEND TRANSACTION行,程序大约需要一百六十秒!因此,对于SQLite中的数据加载,事务是一个巨大的性能提升
  • 在此示例中,事务中的预准备语句比在我的计算机上加载数据库转储
    sqlite3 example.sqlite .dump > backup.sql
    time cat backup.sql | sqlite3 test.sqlite
    需要大约0.8秒更快,而程序大约需要0.25秒