6

我正在创建一个 Ruby 脚本,以将大约 150k 行的制表符分隔文本文件导入 SQLite。到目前为止:

require 'sqlite3'

file = File.new("/Users/michael/catalog.txt")
string = []

# Escape single quotes, remove newline, split on tabs, 
# wrap each item in quotes, and join with commas
def prepare_for_insert(s)
  s.gsub(/'/,"\\\\'").chomp.split(/\t/).map {|str| "'#{str}'"}.join(", ")
end

file.each_line do |line|
  string << prepare_for_insert(line)
end

database = SQLite3::Database.new("/Users/michael/catalog.db")

# Insert each string into the database
string.each do |str|
  database.execute( "INSERT INTO CATALOG VALUES (#{str})")
end

尽管在我的方法gsub中转义单引号,但脚本在包含单引号的第一行出错:prepare_for_insert

/Users/michael/.rvm/gems/ruby-1.9.3-p0/gems/sqlite3-1.3.5/lib/sqlite3/database.rb:91:
in `initialize': near "s": syntax error (SQLite3::SQLException)

它在第 15 行出错。如果我用 来检查该行puts string[14],我可以看到它在“s”附近显示错误的位置。它看起来像这样:'Touch the Top of the World: A Blind Man\'s Journey to Climb Farther Than the Eye Can See'

看起来单引号被转义了,为什么我仍然收到错误?

4

1 回答 1

13

根本不要那样做,字符串插值和 SQL 往往是一个糟糕的组合。改用准备好的语句,让驱动程序处理引用和转义:

# Ditch the gsub in prepare_for_insert and...
db  = SQLite3::Database.new('/Users/michael/catalog.db')
ins = db.prepare('insert into catalog (column_name) values (?)')
string.each { |s| ins.execute(s) }

您当然应该column_name用真实的列名替换;您不必在 INSERT 中指定列名,但无论如何您都应该这样做。如果您需要插入更多列,则将更多占位符和参数添加到ins.execute.

使用prepareandexecute应该更快、更安全、更容易,而且不会让您觉得自己是在 1999 年编写 PHP。

此外,您应该使用标准的 CSV 解析器来解析制表符分隔的文件,XSV 格式处理起来并不好玩(实际上它们是彻头彻尾的邪恶),而且您有更好的事情来处理您的时间而不是处理它们废话和边缘情况等等。

于 2012-03-08T07:42:02.217 回答