Skip to content

Commit

Permalink
Add support for duplicate column names (#21)
Browse files Browse the repository at this point in the history
* add support for duplicate column names

* add more sqlite data conversions

* improve ci

* improve ci

* typo fix
  • Loading branch information
tanner0101 authored Jul 16, 2020
1 parent 4b6bcf4 commit 2b7b24c
Show file tree
Hide file tree
Showing 6 changed files with 175 additions and 61 deletions.
130 changes: 84 additions & 46 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,54 +1,92 @@
name: test
on:
- pull_request
pull_request:
push:
branches:
- master
defaults:
run:
shell: bash
jobs:
sqlite-nio_bionic:
container:
image: vapor/swift:5.2-bionic
dependents:
runs-on: ubuntu-latest
container: swift:5.2-bionic
strategy:
fail-fast: false
matrix:
dependent:
- sqlite-kit
- fluent-sqlite-driver
steps:
- run: apt update -y; apt install -y libsqlite3-dev
- uses: actions/checkout@v1
- run: swift test --enable-test-discovery --sanitize=thread
sqlite-nio_xenial:
container:
image: vapor/swift:5.2-xenial
- name: Install dependencies
run: apt-get -q update && apt-get -q install -y libsqlite3-dev
- name: Check out SQLiteNIO
uses: actions/checkout@v2
with:
path: sqlite-nio
- name: Check out dependent
uses: actions/checkout@v2
with:
repository: vapor/${{ matrix.dependent }}
path: dependent
- name: Use local SQLiteNIO
run: swift package edit sqlite-nio --path sqlite-nio
working-directory: dependent
- name: Run tests with Thread Sanitizer
run: swift test --enable-test-discovery --sanitize=thread
working-directory: dependent
linux:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
image:
# 5.2 Stable
- swift:5.2-xenial
- swift:5.2-bionic
# 5.2 Unstable
- swiftlang/swift:nightly-5.2-xenial
- swiftlang/swift:nightly-5.2-bionic
# 5.3 Unstable
- swiftlang/swift:nightly-5.3-xenial
- swiftlang/swift:nightly-5.3-bionic
# Master Unsable
- swiftlang/swift:nightly-master-xenial
- swiftlang/swift:nightly-master-bionic
- swiftlang/swift:nightly-master-focal
- swiftlang/swift:nightly-master-centos8
- swiftlang/swift:nightly-master-amazonlinux2
container: ${{ matrix.image }}
steps:
- run: apt update -y; apt install -y libsqlite3-dev
- uses: actions/checkout@v1
- run: swift test --enable-test-discovery --sanitize=thread
# sqlite-nio_catalina:
# runs-on: macOS-10.15
# steps:
# - uses: actions/checkout@v1
# - run: swift test --sanitize=thread
# sqlite-nio_ios13:
# runs-on: macOS-10.15
# steps:
# - uses: actions/checkout@v1
# - run: xcodebuild test -destination 'name=iPhone 11' -scheme 'sqlite-nio'
sqlite-kit:
container:
image: vapor/swift:5.2
runs-on: ubuntu-latest
steps:
- run: apt update -y; apt install -y libsqlite3-dev
- run: git clone -b master https://github.com/vapor/sqlite-kit.git
working-directory: ./
- run: swift package edit sqlite-nio --revision ${{ github.sha }}
working-directory: ./sqlite-kit
- run: swift test --enable-test-discovery --sanitize=thread
working-directory: ./sqlite-kit
fluent-sqlite-driver:
container:
image: vapor/swift:5.2
runs-on: ubuntu-latest
- name: Install Ubuntu dependencies
run: apt-get -q update && apt-get -q install -y libsqlite3-dev
if: ${{ endsWith(matrix.image, 'xenial') || endsWith(matrix.image, 'bionic') || endsWith(matrix.image, 'focal') }}
- name: Install CentOS deps
run: dnf install -y sqlite-devel
if: ${{ endsWith(matrix.image, 'centos8') }}
- name: Update AmazonLinux2's too-old SQLite and compensate for its Postgres
if: ${{ endsWith(matrix.image, 'amazonlinux2') }}
working-directory: /root
# Cribbed from the Fedora RPM, leaves out a lot. System's Tcl is too old to run SQLite's tests.
run: |
yum install -y sqlite-devel
yum install -y file tcl-devel make
curl -L 'https://www.sqlite.org/src/tarball/sqlite.tar.gz?r=release' | tar xz && cd sqlite
export CFLAGS="-DSQLITE_DISABLE_DIRSYNC=1 -DSQLITE_SECURE_DELETE=1"
./configure --prefix=/usr --libdir=/usr/lib64 --enable-fts3 --enable-all --with-tcl=/usr/lib64
make all install
- name: Checkout code
uses: actions/checkout@v2
- name: Run tests with Thread Sanitizer
run: swift test --enable-test-discovery --sanitize=thread
macOS:
runs-on: macos-latest
steps:
- run: apt update -y; apt install -y libsqlite3-dev
- run: git clone -b master https://github.com/vapor/fluent-sqlite-driver.git
working-directory: ./
- run: swift package edit sqlite-nio --revision ${{ github.sha }}
working-directory: ./fluent-sqlite-driver
- run: swift test --enable-test-discovery --sanitize=thread
working-directory: ./fluent-sqlite-driver
- name: Select latest available Xcode
uses: maxim-lobanov/[email protected]
with:
xcode-version: latest
- name: Checkout code
uses: actions/checkout@v2
- name: Run tests with Thread Sanitizer
run: swift test --enable-test-discovery --sanitize=thread
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
DerivedData
.swiftpm
Package.resolved
Tests/LinuxMain.swift

39 changes: 39 additions & 0 deletions Sources/SQLiteNIO/SQLiteData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,45 @@ public enum SQLiteData: Equatable, Encodable, CustomStringConvertible {
/// `NULL`.
case null

public var integer: Int? {
switch self {
case .integer(let integer):
return integer
case .float(let double):
return Int(double)
case .text(let string):
return Int(string)
case .blob, .null:
return nil
}
}

public var double: Double? {
switch self {
case .integer(let integer):
return Double(integer)
case .float(let double):
return double
case .text(let string):
return Double(string)
case .blob, .null:
return nil
}
}

public var string: String? {
switch self {
case .integer(let integer):
return String(integer)
case .float(let double):
return String(double)
case .text(let string):
return string
case .blob, .null:
return nil
}
}

/// Description of data
public var description: String {
switch self {
Expand Down
41 changes: 31 additions & 10 deletions Sources/SQLiteNIO/SQLiteRow.swift
Original file line number Diff line number Diff line change
@@ -1,21 +1,42 @@
public struct SQLiteColumns {
public let offsets: [String: Int]
public struct SQLiteColumn: CustomStringConvertible {
public let name: String
public let data: SQLiteData

public var description: String {
"\(self.name): \(self.data)"
}
}

public struct SQLiteRow: CustomStringConvertible {
public let columns: SQLiteColumns
public let data: [SQLiteData]
public struct SQLiteRow {
let columnOffsets: SQLiteColumnOffsets
let data: [SQLiteData]

public var description: String {
return self.columns.offsets
.mapValues { self.data[$0] }
.description
public var columns: [SQLiteColumn] {
self.columnOffsets.offsets.map { (name, offset) in
SQLiteColumn(name: name, data: self.data[offset])
}
}

public func column(_ name: String) -> SQLiteData? {
guard let offset = self.columns.offsets[name] else {
guard let offset = self.columnOffsets.lookupTable[name] else {
return nil
}
return self.data[offset]
}
}

extension SQLiteRow: CustomStringConvertible {
public var description: String {
self.columns.description
}
}

final class SQLiteColumnOffsets {
let offsets: [(String, Int)]
let lookupTable: [String: Int]

init(offsets: [(String, Int)]) {
self.offsets = offsets
self.lookupTable = .init(offsets, uniquingKeysWith: { a, b in a })
}
}
10 changes: 5 additions & 5 deletions Sources/SQLiteNIO/SQLiteStatement.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,22 +50,22 @@ internal struct SQLiteStatement {
}
}

internal func columns() throws -> SQLiteColumns {
var columns: [String: Int] = [:]
internal func columns() throws -> SQLiteColumnOffsets {
var columns: [(String, Int)] = []

let count = sqlite3_column_count(self.handle)
columns.reserveCapacity(Int(count))

// iterate over column count and intialize columns once
// we will then re-use the columns for each row
for i in 0..<count {
try columns[self.column(at: i)] = numericCast(i)
try columns.append((self.column(at: i), numericCast(i)))
}

return .init(offsets: columns)
}

internal func nextRow(for columns: SQLiteColumns) throws -> SQLiteRow? {
internal func nextRow(for columns: SQLiteColumnOffsets) throws -> SQLiteRow? {
// step over the query, this will continue to return SQLITE_ROW
// for as long as there are new rows to be fetched
let step = sqlite3_step(self.handle)
Expand All @@ -89,7 +89,7 @@ internal struct SQLiteStatement {
for i in 0..<count {
try row.append(self.data(at: Int32(i)))
}
return SQLiteRow(columns: columns, data: row)
return SQLiteRow(columnOffsets: columns, data: row)
}

// MARK: Private
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,21 @@ final class SQLiteNIOTests: XCTestCase {
XCTAssertEqual(Date(sqliteData: rows[0].column("date")!)?.description, date.description)
}

func testDuplicateColumnName() throws {
let conn = try SQLiteConnection.open(storage: .memory, threadPool: self.threadPool, on: self.eventLoop).wait()
defer { try! conn.close().wait() }

let rows = try conn.query("SELECT 1 as foo, 2 as foo").wait()
var i = 0
for column in rows[0].columns {
XCTAssertEqual(column.name, "foo")
i += column.data.integer!
}
XCTAssertEqual(i, 3)
XCTAssertEqual(rows[0].column("foo")?.integer, 1)
XCTAssertEqual(rows[0].columns.filter { $0.name == "foo" }[1].data.integer, 2)
}

var threadPool: NIOThreadPool!
var eventLoopGroup: EventLoopGroup!
var eventLoop: EventLoop {
Expand Down

0 comments on commit 2b7b24c

Please sign in to comment.