diff --git a/README.md b/README.md index 478aebc..37aa7ea 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Nim binding for Qt 5's Qt SQL library that integrates with the features of the Nim language. -##Features +## Features * Production-ready * All of the features that you would expect from Qt SQL * Such as a single API for multiple database engines @@ -12,15 +12,48 @@ Nim language. * For instance, instead of providing full bindings for `QVariant`, this provides a minimal binding and lets C++'s type system take care of converting to and from `QVariant` objects automatically. +* Abstracts certain Qt behaviors that are not Nim-like, and presents a +Nim-like API. -##Known Limitations +## Qt Type Conversions +Qt uses a system of default values, null properties, and invalid properties to +indicate the failure of a type conversion. The objects and values produced by +failed conversions often function like regular objects and values, but produce +unexpected results when used. This behavior leads to a silent failure +situation if the result of a Qt type conversion is not checked properly by the +program. + +In Nim, most type conversion errors are not possible due to the type system +and compiler checks. When it is impossible for a type conversion error to be +checked at runtime, the Nim runtime and most Nim procedures will raise +exceptions, which prevents silent failure situations. + +`nim-qt5_qtsql` abstracts the Qt behavior and changes it to the Nim behavior. +When possible, the binding checks for default values, null properties, and +invalid properties after Qt type conversions. If these are present, the +binding will throw a `QObjectConversionError`, which inherits from Nim's +`ObjectConversionError`. Default values, null properties, and invalid +properties are only checked for type conversions. Programs can still +purposefully create Qt objects with default values, null properties, and +invalid properties. + +## Known Limitations * Qt SQL does not deep copy `QSqlQuery` objects, so they must be kept in scope on the stack during usage. +* Automatic conversions from Qt types to Nim types are temporarily disabled in +version 1.1.x of the binding. + * The Nim 0.17.0 compiler produces buggy code for procedures that return + C++ objects. This has been worked around, but the work around requires + changing `converter`s to `proc`s, which disables automatic conversion. + Manual conversion is still possible by calling the conversion procedures + in the binding. + * See [nim-lang/Nim#5140](https://github.com/nim-lang/Nim/issues/5140) + for more details. -##Usage +## Usage See [`qt5_qtsql/tests/sqlcat.nim`](qt5_qtsql/tests/sqlcat.nim) for an example program that reads and writes a SQLite database. -##License +## License This project is licensed under the MIT License. For full license text, see [`LICENSE`](LICENSE). diff --git a/qt5_qtsql.nim b/qt5_qtsql.nim index 32104cb..e0dde69 100644 --- a/qt5_qtsql.nim +++ b/qt5_qtsql.nim @@ -34,6 +34,7 @@ import qt5_qtsql/src/qsqlerror import qt5_qtsql/src/qsqlrecord import qt5_qtsql/src/qsqlquery import qt5_qtsql/src/qsqldatabase +import qt5_qtsql/src/qobjectconversionerror export immutablecstring export qbytearray @@ -46,3 +47,4 @@ export qsqlerror export qsqlrecord export qsqlquery export qsqldatabase +export qobjectconversionerror diff --git a/qt5_qtsql.nimble b/qt5_qtsql.nimble index 9b2e909..3d06c54 100644 --- a/qt5_qtsql.nimble +++ b/qt5_qtsql.nimble @@ -1,6 +1,6 @@ [Package] name = "qt5_qtsql" -version = "1.0.3" +version = "1.1.0" author = "Philip Wernersbach " description = "Binding for Qt 5's Qt SQL library. Provides a single API for multiple database engines." license = "MIT" diff --git a/qt5_qtsql/src/qbytearray.nim b/qt5_qtsql/src/qbytearray.nim index efc3a18..05275a9 100644 --- a/qt5_qtsql/src/qbytearray.nim +++ b/qt5_qtsql/src/qbytearray.nim @@ -24,6 +24,7 @@ # SOFTWARE. import immutablecstring +import qobjectconversionerror const QBYTEARRAY_H = "" @@ -34,9 +35,12 @@ proc isNull*(self: QByteArrayObj): bool {.header: QBYTEARRAY_H, importcpp: "isNu proc isEmpty*(self: QByteArrayObj): bool {.header: QBYTEARRAY_H, importcpp: "isEmpty".} #proc constDataUnsafe(self: QByteArrayObj): cstring {.header: QBYTEARRAY_H, importcpp: "constData".} -proc constData*(self: QByteArrayObj): immutablecstring = +proc constData*(self: QByteArrayObj): immutablecstring {.raises: [QObjectConversionError].} = var mutableData: cstring + if unlikely(self.isNull == true): + raise newException(QObjectConversionError, "Failed to convert QByteArrayObj to immutablecstring, QByteArrayObj is null!") + {.emit: "`mutabledata` = (char *)`self`.constData();".} return (mutableData: mutableData) diff --git a/qt5_qtsql/src/qdatetime.nim b/qt5_qtsql/src/qdatetime.nim index 6ae12e4..cca3fc9 100644 --- a/qt5_qtsql/src/qdatetime.nim +++ b/qt5_qtsql/src/qdatetime.nim @@ -26,6 +26,7 @@ import qstring import qtimezone import qttimespec +import qobjectconversionerror const QDATETIME_H = "" @@ -57,8 +58,22 @@ template newQDateTimeObj*(msecs: qint64, timeSpec: QtTimeSpec): QDateTimeObj = proc currentQDateTimeUtc*(): QDateTimeObj {.header: QDATETIME_H, importcpp: "QDateTime::currentDateTimeUtc".} -proc toQStringObj*(dateTime: QDateTimeObj, format: cstring): QStringObj {.header: QDATETIME_H, importcpp: "toString".} -proc toMSecsSinceEpoch*(dateTime: QDateTimeObj): qint64 {.header: QDATETIME_H, importcpp: "toMSecsSinceEpoch".} +proc internalToQStringObj*(dateTime: QDateTimeObj, format: cstring): QStringObj {.header: QDATETIME_H, importcpp: "toString".} +proc internalToMSecsSinceEpoch*(dateTime: QDateTimeObj): qint64 {.header: QDATETIME_H, importcpp: "toMSecsSinceEpoch".} + +template toQStringObj*(dateTime: QDateTimeObj, format: cstring): QStringObj = #{.raises: [QObjectConversionError].} = + var result = dateTime.internalToQStringObj(format) + + if unlikely(result.isEmpty == true): + raise newException(QObjectConversionError, "Failed to convert QDateTimeObj to QStringObj!") + + result + +proc toMSecsSinceEpoch*(dateTime: QDateTimeObj): qint64 {.raises: [QObjectConversionError].} = + if unlikely(dateTime.isValid == false): + raise newException(QObjectConversionError, "Failed to convert QDateTimeObj to qint64!") + else: + result = dateTime.internalToMSecsSinceEpoch proc setTimeZone*(dateTime: QDateTimeObj, toZone: QTimeZoneObj) {.header: QDATETIME_H, importcpp: "setTimeZone".} proc addMSecs*(a: var QDateTimeObj, b: qint64): QDateTimeObj {.header: QDATETIME_H, importcpp: "addMSecs".} diff --git a/qt5_qtsql/src/qobjectconversionerror.nim b/qt5_qtsql/src/qobjectconversionerror.nim new file mode 100644 index 0000000..3153391 --- /dev/null +++ b/qt5_qtsql/src/qobjectconversionerror.nim @@ -0,0 +1,2 @@ +type + QObjectConversionError* = object of ObjectConversionError diff --git a/qt5_qtsql/src/qsqldatabase.nim b/qt5_qtsql/src/qsqldatabase.nim index a07cbc5..501eb19 100644 --- a/qt5_qtsql/src/qsqldatabase.nim +++ b/qt5_qtsql/src/qsqldatabase.nim @@ -26,11 +26,15 @@ import qbytearray import qstring import qsqlerror +import qobjectconversionerror const QSQLDATABASE_H = "" type QSqlDatabaseObj* {.final, header: QSQLDATABASE_H, importc: "QSqlDatabase".} = object + InvalidQSqlDatabaseException* = object of SystemError + +proc isValid*(self: QSqlDatabaseObj): bool {.header: QSQLDATABASE_H, importcpp: "isValid".} # uc stands for Unsafe Cast template qSqlDatabaseProcWithOneArgThatReturnsVoid(typ: typedesc, name: expr, importcppname: string) = @@ -39,10 +43,26 @@ template qSqlDatabaseProcWithOneArgThatReturnsVoid(typ: typedesc, name: expr, im template qSqlDatabaseProcThatReturnsVoid(name: expr, importcppname: string) = proc `name`*(self: QSqlDatabaseObj) {.header: QSQLDATABASE_H, importcpp: importcppname.} -proc qSqlDatabaseAddDatabase*(typ: cstring): QSqlDatabaseObj {.header: QSQLDATABASE_H, importcpp: "QSqlDatabase::addDatabase(@)".} -proc qSqlDatabaseAddDatabase*(typ: cstring, connectionName: cstring): QSqlDatabaseObj {.header: QSQLDATABASE_H, importcpp: "QSqlDatabase::addDatabase(@)".} +proc internalQSqlDatabaseAddDatabase*(typ: cstring): QSqlDatabaseObj {.header: QSQLDATABASE_H, importcpp: "QSqlDatabase::addDatabase(@)".} +proc internalQSqlDatabaseAddDatabase*(typ: cstring, connectionName: cstring): QSqlDatabaseObj {.header: QSQLDATABASE_H, importcpp: "QSqlDatabase::addDatabase(@)".} proc qSqlDatabaseRemoveDatabase*(connectionName: cstring) {.header: QSQLDATABASE_H, importcpp: "QSqlDatabase::removeDatabase(@)".} +template qSqlDatabaseAddDatabase(typ: cstring): QSqlDatabaseObj = #{.raises: [InvalidQSqlDatabaseException].}= + var result = typ.internalQSqlDatabaseAddDatabase + + if unlikely(result.isValid == false): + raise newException(InvalidQSqlDatabaseException, "QSqlDatabaseObj is invalid!") + + result + +template qSqlDatabaseAddDatabase(typ: cstring, connectionName: cstring): QSqlDatabaseObj = #{.raises: [InvalidQSqlDatabaseException].} = + var result = typ.internalQSqlDatabaseAddDatabase(connectionName) + + if unlikely(result.isValid == false): + raise newException(InvalidQSqlDatabaseException, "QSqlDatabaseObj is invalid!") + + result + #proc cppNew(other: QSqlDatabase): ptr QSqlDatabaseObj {.header: QSQLDATABASE_H, importcpp: "new QSqlDatabase::QSqlDatabase(@)".} #proc cppDelete(self: ptr QSqlDatabaseObj) {.header: QSQLDATABASE_H, importcpp: "delete @".} @@ -76,7 +96,15 @@ template newQSqlDatabase*(typ: cstring, connectionName: cstring): expr = # self.up.cppDelete() # self.up = nil -proc getQSqlDatabase*(connectionName: cstring, open = true): QSqlDatabaseObj {.header: QSQLDATABASE_H, importcpp: "QSqlDatabase::database(@)".} +proc internalGetQSqlDatabase*(connectionName: cstring, open = true): QSqlDatabaseObj {.header: QSQLDATABASE_H, importcpp: "QSqlDatabase::database(@)".} + +template getQSqlDatabase*(connectionName: cstring, open = true): QSqlDatabaseObj = #{.raises: [InvalidQSqlDatabaseException].} = + var result = connectionName.internalGetQSqlDatabase(open) + + if unlikely(result.isValid == false): + raise newException(InvalidQSqlDatabaseException, "QSqlDatabaseObj is invalid!") + + result proc internalOpen(self: QSqlDatabaseObj): bool {.header: QSQLDATABASE_H, importcpp: "open".} proc internalOpen(self: QSqlDatabaseObj, user: cstring, password: cstring): bool {.header: QSQLDATABASE_H, importcpp: "open".} @@ -86,53 +114,41 @@ proc internalTransaction(self: QSqlDatabaseObj): bool {.header: QSQLDATABASE_H, proc internalCommit(self: QSqlDatabaseObj): bool {.header: QSQLDATABASE_H, importcpp: "commit".} proc internalRollback(self: QSqlDatabaseObj): bool {.header: QSQLDATABASE_H, importcpp: "rollback"} -proc isValid*(self: QSqlDatabaseObj): bool {.header: QSQLDATABASE_H, importcpp: "isValid".} - template nativeErrorCodeCString*(self: QSqlErrorObj): expr = self.nativeErrorCode().toUtf8().constData() template textCString*(self: QSqlErrorObj): expr = self.text().toUtf8().constData() -proc open*(self: var QSqlDatabaseObj): bool {.raises: [QSqlException], discardable.} = - let status = self.internalOpen() +proc open*(self: var QSqlDatabaseObj): bool {.raises: [QSqlException, QObjectConversionError], discardable.} = + result = self.internalOpen() - if status != true: + if unlikely(result == false): raise newQSqlError(self.lastError()) - else: - return true -proc open*(self: var QSqlDatabaseObj, user: cstring, password: cstring): bool {.raises: [QSqlException], discardable.} = - let status = self.internalOpen(user, password) +proc open*(self: var QSqlDatabaseObj, user: cstring, password: cstring): bool {.raises: [QSqlException, QObjectConversionError], discardable.} = + result = self.internalOpen(user, password) - if status != true: + if unlikely(result == false): raise newQSqlError(self.lastError()) - else: - return true -proc beginTransaction*(self: var QSqlDatabaseObj): bool {.raises: [QSqlException], discardable.} = - let status = self.internalTransaction() +proc beginTransaction*(self: var QSqlDatabaseObj): bool {.raises: [QSqlException, QObjectConversionError], discardable.} = + result = self.internalTransaction() - if status != true: + if unlikely(result == false): raise newQSqlError(self.lastError()) - else: - return true -proc commitTransaction*(self: var QSqlDatabaseObj): bool {.raises: [QSqlException], discardable.} = - let status = self.internalCommit() +proc commitTransaction*(self: var QSqlDatabaseObj): bool {.raises: [QSqlException, QObjectConversionError], discardable.} = + result = self.internalCommit() - if status != true: + if unlikely(result == false): raise newQSqlError(self.lastError()) - else: - return true -proc rollback*(self: var QSqlDatabaseObj): bool {.raises: [QSqlException], discardable.} = - let status = self.internalRollback() +proc rollback*(self: var QSqlDatabaseObj): bool {.raises: [QSqlException, QObjectConversionError], discardable.} = + result = self.internalRollback() - if status != true: + if unlikely(result == false): raise newQSqlError(self.lastError()) - else: - return true qSqlDatabaseProcWithOneArgThatReturnsVoid(cstring, setHostName, "setHostName") qSqlDatabaseProcWithOneArgThatReturnsVoid(cint, setPort, "setPort") diff --git a/qt5_qtsql/src/qsqlquery.nim b/qt5_qtsql/src/qsqlquery.nim index b94c7c7..02389e3 100644 --- a/qt5_qtsql/src/qsqlquery.nim +++ b/qt5_qtsql/src/qsqlquery.nim @@ -28,6 +28,7 @@ import qvariant import qstring import qsqlerror import qsqldatabase +import qobjectconversionerror const QSQLQUERY_H = "" @@ -35,9 +36,7 @@ type QSqlQueryObj* {.final, header: QSQLQUERY_H, importc: "QSqlQuery".} = object proc qSqlQuery*(db: var QSqlDatabaseObj): QSqlQueryObj {.header: QSQLQUERY_H, importcpp: "QSqlQuery::QSqlQuery(@)".} -proc newQSqlQuery*(query: cstring, db: var QSqlDatabaseObj): QSqlQueryObj {.header: QSQLQUERY_H, importcpp: "QSqlQuery::QSqlQuery(@)"} -proc qSqlQuery*(db: var QSqlDatabaseObj, query: cstring): QSqlQueryObj = - newQSqlQuery(query, db) +proc qSqlQuery*(query: cstring, db: var QSqlDatabaseObj): QSqlQueryObj {.header: QSQLQUERY_H, importcpp: "QSqlQuery::QSqlQuery(@)"} proc internalPrepare(self: QSqlQueryObj, query: cstring): bool {.header: QSQLQUERY_H, importcpp: "prepare".} @@ -59,33 +58,53 @@ proc internalExec(self: QSqlQueryObj): bool {.header: QSQLQUERY_H, importcpp: "e proc lastError*(self: QSqlQueryObj): QSqlErrorObj {.header: QSQLQUERY_H, importcpp: "lastError".} -proc exec*(self: var QSqlQueryObj, query: cstring): bool {.raises: [QSqlException], discardable.} = - let status = self.internalExec(query) +proc exec*(self: var QSqlQueryObj, query: cstring): bool {.raises: [QSqlException, QObjectConversionError], discardable.} = + result = self.internalExec(query) - if status != true: + if unlikely(result == false): raise newQSqlError(self.lastError()) - else: - return true -proc exec*(self: var QSqlQueryObj): bool {.raises: [QSqlException], discardable.} = - let status = self.internalExec() +proc exec*(self: var QSqlQueryObj): bool {.raises: [QSqlException, QObjectConversionError], discardable.} = + result = self.internalExec() - if status != true: + if unlikely(result == false): raise newQSqlError(self.lastError()) - else: - return true -proc prepare*(self: var QSqlQueryObj, query: cstring): bool {.raises: [QSqlException], discardable.} = - let status = self.internalPrepare(query) +proc prepare*(self: var QSqlQueryObj, query: cstring): bool {.raises: [QSqlException, QObjectConversionError], discardable.} = + result = self.internalPrepare(query) - if status != true: + if unlikely(result == false): raise newQSqlError(self.lastError()) - else: - return true + +proc internalValue*(self: QSqlQueryObj, index: cint): QVariantObj {.header: QSQLQUERY_H, importcpp: "value".} +proc internalValue*(self: QSqlQueryObj, index: cstring): QVariantObj {.header: QSQLQUERY_H, importcpp: "value".} proc next*(self: QSqlQueryObj): bool {.header: QSQLQUERY_H, importcpp: "next".} -proc value*(self: QSqlQueryObj, index: cint): QVariantObj {.header: QSQLQUERY_H, importcpp: "value".} -proc value*(self: QSqlQueryObj, index: cstring): QVariantObj {.header: QSQLQUERY_H, importcpp: "value".} + +when compileOption("boundChecks"): + template value*(query: QSqlQueryObj, index: cint): QVariantObj = #{.raises: [IndexError].} = + var result = query.internalValue(index) + + if unlikely(result.isValid == false): + raise newException(IndexError, "Field " & $(index + 1) & " requested from current query record, but the field is invalid!") + + result + + template value*(query: QSqlQueryObj, name: cstring): QVariantObj = #{.raises: [IndexError].} = + var result = query.internalValue(name) + + if unlikely(result.isValid == false): + raise newException(IndexError, "Field with name \"" & $name & "\" requested from current query record, but the field is invalid!") + + result +else: + template value*(query: QSqlQueryObj, index: cint): QVariantObj = + query.internalValue(index) + + template value*(query: QSqlQueryObj, name: cstring): QVariantObj = + query.internalValue(name) + proc isNull*(self: QSqlQueryObj, index: cint): bool {.header: QSQLQUERY_H, importcpp: "isNull".} proc isNull*(self: QSqlQueryObj, name: cstring): bool {.header: QSQLQUERY_H, importcpp: "isNull".} proc isValid*(self: QSqlQueryObj): bool {.header: QSQLQUERY_H, importcpp: "isValid".} +proc isActive*(self: QSqlQueryObj): bool {.header: QSQLQUERY_H, importcpp: "isActive".} diff --git a/qt5_qtsql/src/qsqlrecord.nim b/qt5_qtsql/src/qsqlrecord.nim index 641e453..d8b7b1f 100644 --- a/qt5_qtsql/src/qsqlrecord.nim +++ b/qt5_qtsql/src/qsqlrecord.nim @@ -32,12 +32,51 @@ const QSQLRECORD_H = "" type QSqlRecordObj* {.final, header: QSQLRECORD_H, importc: "QSqlRecord".} = object -proc record*(query: QSqlQueryObj): QSqlRecordObj {.header: QSQLRECORD_H, importcpp: "record".} +proc internalRecord*(query: QSqlQueryObj): QSqlRecordObj {.header: QSQLRECORD_H, importcpp: "record".} +proc internalFieldName*(record: QSqlRecordObj, index: cint): QStringObj {.header: QSQLRECORD_H, importcpp: "fieldName".} +proc internalValue*(record: QSqlRecordObj, index: cint): QVariantObj {.header: QSQLRECORD_H, importcpp: "value".} +proc internalValue*(record: QSqlRecordObj, name: cstring): QVariantObj {.header: QSQLRECORD_H, importcpp: "value".} + +template record(query: QSqlQueryObj): QSqlRecordObj = #{.raises: [FieldError].} = + if unlikely((query.isValid == false) or (query.isActive == false)): + raise newException(FieldError, "Failed to create QSqlRecordObj from current QSqlQueryObj state!") + + query.internalRecord proc count*(record: QSqlRecordObj): cint {.header: QSQLRECORD_H, importcpp: "count".} -proc fieldName*(record: QSqlRecordObj, index: cint): QStringObj {.header: QSQLRECORD_H, importcpp: "fieldName".} -proc value*(record: QSqlRecordObj, index: cint): QVariantObj {.header: QSQLRECORD_H, importcpp: "value".} -proc value*(record: QSqlRecordObj, name: cstring): QVariantObj {.header: QSQLRECORD_H, importcpp: "value".} + +when compileOption("boundChecks"): + template fieldName*(record: QSqlRecordObj, index: cint): QStringObj = #{.raises: [IndexError].} = + if unlikely(record.count <= index): + raise newException(IndexError, "Name for field " & $(index + 1) & " requested from record, but record only has " & $(record.count) & " fields!") + + record.internalFieldName(index) + + template value*(record: QSqlRecordObj, index: cint): QVariantObj = #{.raises: [IndexError].} = + var result = record.internalValue(index) + + if unlikely(result.isValid == false): + raise newException(IndexError, "Field " & $(index + 1) & " requested from record, but record only has " & $(record.count) & " fields!") + + result + + template value*(record: QSqlRecordObj, name: cstring): QVariantObj = #{.raises: [IndexError].} = + var result = record.internalValue(name) + + if unlikely(result.isValid == false): + raise newException(IndexError, "Field with name \"" & $name & "\" requested from record, but no such field exists!") + + result +else: + template fieldName*(record: QSqlRecordObj, index: cint): QStringObj = + record.internalFieldName(index) + + template value*(record: QSqlRecordObj, index: cint): QVariantObj = + record.internalValue(index) + + template value*(record: QSqlRecordObj, name: cstring): QVariantObj = + record.internalValue(name) + proc isNull*(record: QSqlRecordObj, index: cint): bool {.header: QSQLRECORD_H, importcpp: "isNull".} proc isNull*(record: QSqlRecordObj, name: cstring): bool {.header: QSQLRECORD_H, importcpp: "isNull".} proc isEmpty*(record: QSqlRecordObj): bool {.header: QSQLRECORD_H, importcpp: "isEmpty".} diff --git a/qt5_qtsql/src/qstring.nim b/qt5_qtsql/src/qstring.nim index c346d43..ab22a16 100644 --- a/qt5_qtsql/src/qstring.nim +++ b/qt5_qtsql/src/qstring.nim @@ -24,12 +24,15 @@ # SOFTWARE. import qbytearray +import qobjectconversionerror const QSTRING_H = "" type QStringObj* {.final, header: QSTRING_H, importc: "QString".} = object +proc internalToUtf8*(self: QStringObj): QByteArrayObj {.header: QSTRING_H, importcpp: "toUtf8".} + # Qt recommends to always use isEmpty() and avoid isNull(). # # See the "Distinction Between Null and Empty Strings" section of @@ -37,7 +40,14 @@ type proc isNull*(self: QStringObj): bool {.header: QSTRING_H, importcpp: "isNull".} proc isEmpty*(self: QStringObj): bool {.header: QSTRING_H, importcpp: "isEmpty".} -proc toUtf8*(self: QStringObj): QByteArrayObj {.header: QSTRING_H, importcpp: "toUtf8".} +template toUtf8*(self: QStringObj): QByteArrayObj = #{.raises: [QObjectConversionError].} = + var result = self.internalToUtf8 + + if unlikely(result.isNull == true): + raise newException(QObjectConversionError, "Failed to convert QStringObj to UTF-8 QByteArrayObj!") + + result + proc `==`*(self: QStringObj, other: cstring): bool {.header: QSTRING_H, importcpp: "operator==".} proc `==`*(self: QStringObj, other: var QByteArrayObj): bool {.header: QSTRING_H, importcpp: "operator==".} diff --git a/qt5_qtsql/src/qvariant.nim b/qt5_qtsql/src/qvariant.nim index 3763859..1ef2114 100644 --- a/qt5_qtsql/src/qvariant.nim +++ b/qt5_qtsql/src/qvariant.nim @@ -25,6 +25,7 @@ import qstring import qdatetime +import qobjectconversionerror const QVARIANT_H = "" @@ -33,35 +34,90 @@ type qlonglong* {.final, importc: "qulonglong"} = clonglong QVariantObj* {.final, header: QVARIANT_H, importc: "QVariant".} = object -proc internalToQulonglong(x: QVariantObj, ok: ptr bool): qulonglong {.header: QVARIANT_H, importcpp: "toULongLong"} -proc internalToQlonglong(x: QVariantObj, ok: ptr bool): qlonglong {.header: QVARIANT_H, importcpp: "toLongLong"} +proc internalToQulonglong*(x: QVariantObj, ok: ptr bool): qulonglong {.header: QVARIANT_H, importcpp: "toULongLong"} +proc internalToQlonglong*(x: QVariantObj, ok: ptr bool): qlonglong {.header: QVARIANT_H, importcpp: "toLongLong"} +proc internalToQStringObj*(x: QVariantObj): QStringObj {.header: QVARIANT_H, importcpp: "toString".} +proc internalToQDateTimeObj*(variant: QVariantObj): QDateTimeObj {.header: QVARIANT_H, importcpp: "toDateTime"} +proc internalToBool*(variant: QVariantObj): bool {.header: QVARIANT_H, importcpp: "toBool"} +proc internalToFloat*(variant: QVariantObj, ok: ptr bool): float64 {.header: QVARIANT_H, importcpp: "toFloat"} +proc internalToDouble*(variant: QVariantObj, ok: ptr bool): float64 {.header: QVARIANT_H, importcpp: "toDouble"} +proc internalToQVariantObj*[T](x: T): QVariantObj {.header: QVARIANT_H, importcpp: "QVariant::QVariant(@)"} proc isNull*(variant: QVariantObj): bool {.header: QVARIANT_H, importcpp: "isNull".} proc isValid*(variant: QVariantObj): bool {.header: QVARIANT_H, importcpp: "isValid".} +proc canConvert*(variant: QVariantObj, typ: typedesc): bool {.header: QVARIANT_H, importcpp: "#.canConvert<'*2>()".} -converter toQStringObj*(x: QVariantObj): QStringObj {.header: QVARIANT_H, importcpp: "toString".} -converter toQDateTimeObj*(variant: QVariantObj): QDateTimeObj {.header: QVARIANT_H, importcpp: "toDateTime"} -converter toBool*(variant: QVariantObj): bool {.header: QVARIANT_H, importcpp: "toBool"} -converter toFloat*(variant: QVariantObj): float64 {.header: QVARIANT_H, importcpp: "toFloat"} +template toQStringObj*(variant: QVariantObj): QStringObj = #{.raises: [QObjectConversionError]} = + if unlikely(variant.canConvert(QStringObj) == false): + raise newException(QObjectConversionError, "Failed to convert QVariantObj to QStringObj!") + + variant.internalToQStringObj -converter toQulonglong*(x: QVariantObj): qulonglong {.raises: [ObjectConversionError]} = - var ok: bool = false - var data = x.internalToQuLongLong(addr(ok)) +template toQDateTimeObj*(variant: QVariantObj): QDateTimeObj = #{.raises: [QObjectConversionError]} = + var result: QDateTimeObj - if ok != true: - raise newException(ObjectConversionError, "Failed to convert QVariantObj to qulonglong!") + if unlikely(variant.canConvert(QDateTimeObj) == false): + raise newException(QObjectConversionError, "Failed to convert QVariantObj to QDateTimeObj!") else: - return data + result = variant.internalToQDateTimeObj -converter toQlonglong*(x: QVariantObj): qlonglong {.raises: [ObjectConversionError]} = - var ok: bool = false - var data = x.internalToQLongLong(addr(ok)) + if unlikely(result.isValid == false): + raise newException(QObjectConversionError, "Failed to convert QVariantObj to QDateTimeObj!") - if ok != true: - raise newException(ObjectConversionError, "Failed to convert QVariantObj to qlonglong!") + result + +converter toBool*(variant: QVariantObj): bool {.raises: [QObjectConversionError]} = + if unlikely(variant.canConvert(bool) == false): + raise newException(QObjectConversionError, "Failed to convert QVariantObj to bool!") + else: + result = variant.internalToBool + +proc toFloat*(variant: QVariantObj): float64 {.raises: [QObjectConversionError]} = + if unlikely(variant.canConvert(float64) == false): + raise newException(QObjectConversionError, "Failed to convert QVariantObj to float64 via toFloat!") + else: + var ok: bool = false + result = variant.internalToFloat(addr(ok)) + + if unlikely(ok == false): + raise newException(QObjectConversionError, "Failed to convert QVariantObj to float64 via toFloat!") + +proc toDouble*(variant: QVariantObj): float64 {.raises: [QObjectConversionError]} = + if unlikely(variant.canConvert(float64) == false): + raise newException(QObjectConversionError, "Failed to convert QVariantObj to float64 via toDouble!") else: - return data + var ok: bool = false + result = variant.internalToDouble(addr(ok)) + + if unlikely(ok == false): + raise newException(QObjectConversionError, "Failed to convert QVariantObj to float64 via toDouble!") + +proc toQulonglong*(variant: QVariantObj): qulonglong {.raises: [QObjectConversionError]} = + if unlikely(variant.canConvert(qulonglong) == false): + raise newException(QObjectConversionError, "Failed to convert QVariantObj to qulonglong!") + else: + var ok: bool = false + result = variant.internalToQulonglong(addr(ok)) + + if unlikely(ok == false): + raise newException(QObjectConversionError, "Failed to convert QVariantObj to qulonglong!") + +proc toQlonglong*(variant: QVariantObj): qlonglong {.raises: [QObjectConversionError]} = + if unlikely(variant.canConvert(qlonglong) == false): + raise newException(QObjectConversionError, "Failed to convert QVariantObj to qulonglong!") + else: + var ok: bool = false + result = variant.internalToQlonglong(addr(ok)) + + if unlikely(ok == false): + raise newException(QObjectConversionError, "Failed to convert QVariantObj to qulonglong!") + +template toQVariantObj*[T](x: T): QVariantObj = #{.raises: [QObjectConversionError]} = + var result = x.internalToQVariantObj + + if unlikely(result.isValid == false): + raise newException(QObjectConversionError, "Failed to convert value to QVariantObj!") -proc toQVariantObj*[T](x: T): QVariantObj {.header: QVARIANT_H, importcpp: "QVariant::QVariant(@)"} + result proc userType*(variant: QVariantObj): cint {.header: QVARIANT_H, importcpp: "userType".} diff --git a/qt5_qtsql/tests/sqlcat.nim b/qt5_qtsql/tests/sqlcat.nim index d493b1d..5888640 100644 --- a/qt5_qtsql/tests/sqlcat.nim +++ b/qt5_qtsql/tests/sqlcat.nim @@ -71,43 +71,47 @@ proc insertLine(database: var QSqlDatabaseObj, line: string) = insertQuery.bindValue(1, line) insertQuery.exec() -try: - var database = newQSqlDatabase(DATABASE_DRIVER, CONNECTION_NAME_PREFIX & "0") - database.setDatabaseName(DATABASE_NAME) - database.open() +proc sqlcat(): int = + try: + var database = newQSqlDatabase(DATABASE_DRIVER, CONNECTION_NAME_PREFIX & "0") + database.setDatabaseName(DATABASE_NAME) + database.open() - var dropQuery = database.qSqlQuery() - dropQuery.prepare(DROP_QUERY) - dropQuery.exec() + var dropQuery = database.qSqlQuery() + dropQuery.prepare(DROP_QUERY) + dropQuery.exec() - var createQuery = database.qSqlQuery() - createQuery.prepare(CREATE_QUERY) - createQuery.exec() + var createQuery = database.qSqlQuery() + createQuery.prepare(CREATE_QUERY) + createQuery.exec() - for line in stdin.lines: - database.insertLine(line) + for line in stdin.lines: + database.insertLine(line) - database.selectLines(line.hash(), proc(lines: QSqlQueryObj): bool = - while lines.next() == true: - var dbLine = lines.value(0) + database.selectLines(line.hash(), proc(lines: QSqlQueryObj): bool = + while lines.next() == true: + var dbLine = lines.value(0) - # Qt's way to convert a QVariant to a char * is kind of hard, we should probably - # provide a convience function for this. - let dbLineString = dbLine.toQStringObj().toUtf8().constData().umc() - echo dbLineString + # Qt's way to convert a QVariant to a char * is kind of hard, we should probably + # provide a convience function for this. + let dbLineString = dbLine.toQStringObj().toUtf8().constData().umc() + echo dbLineString - return true - ) + return true + ) -except QSqlException: - let e = getCurrentException() - stderr.write(e.getStackTrace()) - stderr.write("Error: unhandled exception: ") - stderr.writeln(getCurrentExceptionMsg()) + result = QuitSuccess + except QSqlException, QObjectConversionError: + let e = getCurrentException() + stderr.write(e.getStackTrace()) + stderr.write("Error: unhandled exception: ") + stderr.writeln(getCurrentExceptionMsg()) - stderr.writeln("") - quit(QuitFailure) + stderr.writeln("") -quit(QuitSuccess) + result = QuitFailure + +var returnValue = sqlcat() +quit(returnValue) # Database connection closes automatically when it goes out of scope.