├── Example ├── README.md ├── Example.pro ├── testmodel.cpp ├── testmodel.h └── main.cpp ├── docs ├── .nojekyll ├── 404.md ├── _images │ ├── logo.ico │ └── pro_arch.png ├── _coverpage.md ├── _sidebar.md ├── version │ └── README.md ├── intro │ └── README.md ├── quick_start │ └── how-use │ │ └── README.md ├── _res │ ├── ext_sc.js │ ├── copy_code.js │ ├── doc-pagination.min.js │ ├── zoom-image.js │ ├── prism.css │ ├── search.js │ ├── vue.css │ └── emoji.js └── index.html ├── .gitignore ├── scripts ├── 系统架构.bmpr └── pro_arch.png ├── NORM.pro ├── NORM ├── inc │ ├── NOrm_global.h │ ├── NOrmWhere_p.h │ ├── NOrmModel.h │ ├── NOrmWhere.h │ ├── NOrm_p.h │ ├── NOrm.h │ ├── NOrmQuerySet_p.h │ ├── NOrmMetaModel.h │ └── NOrmQuerySet.h ├── NORM_inc.pri ├── NORM_src.pri ├── NORM_resource.rc ├── NORM.pro └── src │ ├── NOrmModel.cpp │ ├── NOrm.cpp │ ├── NOrmQuerySet.cpp │ ├── NOrmWhere.cpp │ └── NOrmMetaModel.cpp ├── CMakeLists.txt └── README.md /Example/README.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/404.md: -------------------------------------------------------------------------------- 1 | 说出来你可能不信,我是知识荒原 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Makefile 2 | /*.user 3 | -------------------------------------------------------------------------------- /scripts/系统架构.bmpr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daodaoliang/NORM/HEAD/scripts/系统架构.bmpr -------------------------------------------------------------------------------- /scripts/pro_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daodaoliang/NORM/HEAD/scripts/pro_arch.png -------------------------------------------------------------------------------- /docs/_images/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daodaoliang/NORM/HEAD/docs/_images/logo.ico -------------------------------------------------------------------------------- /NORM.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | CONFIG += ordered 4 | 5 | SUBDIRS = NORM \ 6 | Example 7 | -------------------------------------------------------------------------------- /docs/_images/pro_arch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daodaoliang/NORM/HEAD/docs/_images/pro_arch.png -------------------------------------------------------------------------------- /docs/_coverpage.md: -------------------------------------------------------------------------------- 1 | ![logo](_images/logo.ico) 2 | 3 | # NORM :tm: 4 | 5 | **Qt下的ORM框架** 6 | 7 | [开始阅读](/intro/) 8 | [源码位置](https://github.com/daodaoliang/NORM) 9 | -------------------------------------------------------------------------------- /docs/_sidebar.md: -------------------------------------------------------------------------------- 1 | * [**首页**](/) 2 | 3 | * [**框架简介**](/intro/) 4 | 5 | * **快速入门** 6 | * [如何编译运行](/quick_start/how-use/) 7 | 8 | * 版本历史 9 | * [版本历史](/version/) 10 | 11 | -------------------------------------------------------------------------------- /docs/version/README.md: -------------------------------------------------------------------------------- 1 | !> 当前最新版本 V 1.0.2.0 2 | 3 | ?> V 1.0.2.0 4 | 5 | * 增加数据库的自动创建; 6 | * 解决metaobject中方法的编译错误; 7 | * 修改原项目的方法名字,解除它和员框架的依赖; 8 | 9 | ?> V 1.0.1.0 10 | 11 | * 在 QDjango 中迁移出 db 子项目; 12 | * 修正工程文件的编译依赖关系; 13 | * 增加第三方引用时的工程文件,并增加测试用例; 14 | 15 | 16 | -------------------------------------------------------------------------------- /docs/intro/README.md: -------------------------------------------------------------------------------- 1 | 2 | **NORM**项目 3 | 4 | ?> 系统的基础信息 :arrow_down: 5 | 6 | |基础信息|信息描述| 7 | |:------|:------| 8 | |当前开发版本|V 1.0.2.0| 9 | |代码仓库位置|https://github.com/daodaoliang/NORM| 10 | |开发语言|Qt c++| 11 | |支持的数据库|MySqlServer、SQLite、PostgreSQL、MSSqlServer| 12 | |开发者的开发环境|windows + Qt5.10 + mingw| 13 | 14 | -------------------------------------------------------------------------------- /NORM/inc/NOrm_global.h: -------------------------------------------------------------------------------- 1 | #ifndef NORM_GLOBAL_H 2 | #define NORM_GLOBAL_H 3 | /* 4 | * 描述: NORM api 库接口定义 5 | * 作者: daodaoliang@yeah.net 6 | * 时间: 2021-07-16 7 | */ 8 | 9 | #if defined(NORM_LIBRARY) 10 | # define NORMSHARED_EXPORT Q_DECL_EXPORT 11 | #else 12 | # define NORMSHARED_EXPORT Q_DECL_IMPORT 13 | #endif 14 | 15 | #endif // NORM_GLOBAL_H 16 | -------------------------------------------------------------------------------- /NORM/NORM_inc.pri: -------------------------------------------------------------------------------- 1 | #------------------------------------------------- 2 | # 3 | # Project created by daodaoliang 2018-07-5T11:08:28 4 | # 5 | #------------------------------------------------- 6 | 7 | INCLUDEPATH += $$PWD\inc 8 | 9 | win32{ 10 | LIBS += -L$$PWD/../bin/ -lnorm_sql 11 | DEPENDPATH += $$PWD/../bin 12 | } 13 | 14 | unix{ 15 | LIBS += -L$$PWD/../bin/ -lnorm_sql 16 | DEPENDPATH += $$PWD/../bin 17 | } 18 | 19 | -------------------------------------------------------------------------------- /docs/quick_start/how-use/README.md: -------------------------------------------------------------------------------- 1 | # ORM组件使用说明 2 | 3 | 4 | ### 0. 组件结构说明 5 | 6 | ![项目架构](./../../_images/pro_arch.png) 7 | 8 | ### 1. 组件使用说明 9 | 10 | #### 1.1 编译组件库 11 | 12 | * 获取源码; 13 | * 在QtCreator中打开NORM.pro, 不要选择影子构建,开始编译构建; 14 | * 同级目录中会出现bin文件夹,里面就是编译好的组件库, 注意看版本信息; 15 | * 在外部项目使用时直接把编译好的库和inc文件夹下的文件打包使用即可; 16 | * 在内部项目使用时,请直接引用NORM_inc.pri即可,同时需要手动把编译好的库拷贝到主程序执行目录,或者编写辅助脚本在运行前拷贝; 17 | 18 | #### 1.2 运行测试用例 19 | 20 | * 编译并运行 example 子项目即可,不要勾选在终端运行; 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /NORM/NORM_src.pri: -------------------------------------------------------------------------------- 1 | HEADERS += \ 2 | $$PWD/inc/NOrm.h \ 3 | $$PWD/inc/NOrm_p.h \ 4 | $$PWD/inc/NOrmMetaModel.h \ 5 | $$PWD/inc/NOrmModel.h \ 6 | $$PWD/inc/NOrmQuerySet.h \ 7 | $$PWD/inc/NOrmQuerySet_p.h \ 8 | $$PWD/inc/NOrmWhere.h \ 9 | $$PWD/inc/NOrmWhere_p.h \ 10 | $$PWD/inc/NOrm_global.h 11 | SOURCES += \ 12 | $$PWD/src/NOrm.cpp \ 13 | $$PWD/src/NOrmMetaModel.cpp \ 14 | $$PWD/src/NOrmModel.cpp \ 15 | $$PWD/src/NOrmQuerySet.cpp \ 16 | $$PWD/src/NOrmWhere.cpp 17 | -------------------------------------------------------------------------------- /NORM/inc/NOrmWhere_p.h: -------------------------------------------------------------------------------- 1 | #ifndef NORM_WHERE_P_H 2 | #define NORM_WHERE_P_H 3 | 4 | #include 5 | #include "NOrmWhere.h" 6 | 7 | class NOrmWherePrivate : public QSharedData 8 | { 9 | public: 10 | static QString operationToString(NOrmWhere::Operation operation); 11 | 12 | enum Combine 13 | { 14 | NoCombine, 15 | AndCombine, 16 | OrCombine 17 | }; 18 | static QString combineToString(Combine combine); 19 | 20 | NOrmWherePrivate(); 21 | 22 | QString key; 23 | NOrmWhere::Operation operation; 24 | QVariant data; 25 | 26 | QList children; 27 | Combine combine; 28 | bool negate; 29 | }; 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /docs/_res/ext_sc.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | function handleExternalScript() { 3 | var container = Docsify.dom.getNode('#main'); 4 | var scripts = Docsify.dom.findAll(container, 'script'); 5 | 6 | for (var i = scripts.length; i--;) { 7 | var script = scripts[i]; 8 | 9 | if (script && script.src) { 10 | var newScript = document.createElement('script'); 11 | 12 | Array.prototype.slice.call(script.attributes).forEach(function (attribute) { 13 | newScript[attribute.name] = attribute.value; 14 | }); 15 | 16 | script.parentNode.insertBefore(newScript, script); 17 | script.parentNode.removeChild(script); 18 | } 19 | } 20 | } 21 | 22 | var install = function (hook) { 23 | hook.doneEach(handleExternalScript); 24 | }; 25 | 26 | window.$docsify.plugins = [].concat(install, window.$docsify.plugins); 27 | 28 | }()); -------------------------------------------------------------------------------- /NORM/inc/NOrmModel.h: -------------------------------------------------------------------------------- 1 | #ifndef NORM_MODEL_H 2 | #define NORM_MODEL_H 3 | 4 | /* 5 | * 描述: NORM 数据表模型 6 | * 作者: daodaoliang@yeah.net 7 | * 时间: 2021-07-16 8 | */ 9 | 10 | #include 11 | #include 12 | #include "NOrm_p.h" 13 | #include "saveinthread.h" 14 | 15 | class NOrmModel : public QObject 16 | { 17 | Q_OBJECT 18 | Q_PROPERTY(QVariant pk READ pk WRITE setPk) 19 | Q_CLASSINFO("pk", "ignore_field=true") 20 | 21 | public: 22 | NOrmModel(QObject *parent = nullptr); 23 | 24 | // 主键 25 | QVariant pk() const; 26 | void setPk(const QVariant &pk); 27 | 28 | public slots: 29 | 30 | // 删除 31 | bool remove(); 32 | 33 | // 保存 34 | bool save(); 35 | 36 | // 转打印字串 37 | QString toString() const; 38 | 39 | protected: 40 | QObject *foreignKey(const char *name) const; 41 | void setForeignKey(const char *name, QObject *value); 42 | }; 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1.0) 2 | 3 | project(NORM) 4 | 5 | set(CMAKE_AUTOMOC ON) 6 | 7 | find_package(Qt5Core) 8 | find_package(Qt5Sql) 9 | 10 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 11 | 12 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 13 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 14 | set(NORM_INCLUDE_DIRECTORY ${CMAKE_SOURCE_DIR}/NORM/inc) 15 | 16 | aux_source_directory(${CMAKE_SOURCE_DIR}/NORM/src NORM_SRC) 17 | file(GLOB NORM_INC ${CMAKE_SOURCE_DIR}/NORM/inc/*.h) 18 | 19 | add_library(norm SHARED ${NORM_SRC} ${NORM_INC}) 20 | 21 | include_directories(${CMAKE_SOURCE_DIR}/NORM/inc) 22 | 23 | target_link_libraries(norm Qt5::Core Qt5::Sql) 24 | 25 | install(CODE "FILE(MAKE_DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY})") 26 | 27 | install(DIRECTORY ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/ DESTINATION lib USE_SOURCE_PERMISSIONS) 28 | install(DIRECTORY ${NORM_INCLUDE_DIRECTORY}/ DESTINATION include/norm USE_SOURCE_PERMISSIONS) 29 | -------------------------------------------------------------------------------- /NORM/NORM_resource.rc: -------------------------------------------------------------------------------- 1 | # if defined(UNDER_CE) 2 | # include 3 | # else 4 | # include 5 | # endif 6 | 7 | VS_VERSION_INFO VERSIONINFO 8 | FILEVERSION 1,0,3,0 9 | PRODUCTVERSION 1,0,3,0 10 | FILEFLAGSMASK 0x3fL 11 | #ifdef _DEBUG 12 | FILEFLAGS VS_FF_DEBUG 13 | #else 14 | FILEFLAGS 0x0L 15 | #endif 16 | FILEOS VOS__WINDOWS32 17 | FILETYPE VFT_DLL 18 | FILESUBTYPE 0x0L 19 | BEGIN 20 | BLOCK "StringFileInfo" 21 | BEGIN 22 | BLOCK "040904b0" 23 | BEGIN 24 | VALUE "CompanyName", "NS\0" 25 | VALUE "FileDescription", "ORM\0" 26 | VALUE "FileVersion", "1.0.1.0\0" 27 | VALUE "LegalCopyright", "@2018(daodaoliang@yeah.net)\0" 28 | VALUE "OriginalFilename", "NDBPool1.dll\0" 29 | VALUE "ProductName", "NORM\0" 30 | VALUE "ProductVersion", "1.0.3.0\0" 31 | END 32 | END 33 | BLOCK "VarFileInfo" 34 | BEGIN 35 | VALUE "Translation", 0x0409, 1200 36 | END 37 | END 38 | /* End of Version info */ 39 | 40 | -------------------------------------------------------------------------------- /Example/Example.pro: -------------------------------------------------------------------------------- 1 | QT -= gui 2 | QT += sql 3 | 4 | CONFIG += c++11 console 5 | CONFIG -= app_bundle 6 | TARGET = orm_example 7 | DEFINES += QT_DEPRECATED_WARNINGS 8 | 9 | # 引入第三方库 10 | include($$PWD/../NORM/NORM_inc.pri) 11 | 12 | # include path 13 | INCLUDEPATH += $$PWD/../NORM/inc/ 14 | 15 | # include sources 16 | SOURCES += main.cpp \ 17 | testmodel.cpp 18 | 19 | HEADERS += \ 20 | testmodel.h 21 | 22 | # 输出定义 23 | win32{ 24 | CONFIG += debug_and_release 25 | CONFIG(release, debug|release) { 26 | target_path = ./build_/dist 27 | } else { 28 | target_path = ./build_/debug 29 | } 30 | DESTDIR = $$PWD/../bin 31 | MOC_DIR = $$target_path/moc 32 | RCC_DIR = $$target_path/rcc 33 | OBJECTS_DIR = $$target_path/obj 34 | } 35 | 36 | unix{ 37 | CONFIG += debug_and_release 38 | CONFIG(release, debug|release) { 39 | target_path = ./build_/dist 40 | } else { 41 | target_path = ./build_/debug 42 | } 43 | DESTDIR = $$PWD/../bin/ 44 | MOC_DIR = $$target_path/moc 45 | RCC_DIR = $$target_path/rcc 46 | OBJECTS_DIR = $$target_path/obj 47 | } 48 | -------------------------------------------------------------------------------- /NORM/NORM.pro: -------------------------------------------------------------------------------- 1 | QT -= gui 2 | QT += sql 3 | 4 | TEMPLATE = lib 5 | CONFIG += shared 6 | TARGET = norm_sql 7 | DEFINES += NORM_LIBRARY 8 | CONFIG += c++11 9 | 10 | # 版本号 11 | win32{ 12 | RC_FILE += ./NORM_resource.rc 13 | } 14 | unix{ 15 | VERSION = 1.0.4 16 | } 17 | 18 | # 源码信息 19 | include($$PWD/NORM_src.pri) 20 | INCLUDEPATH += ./inc/ \ 21 | 22 | # 输出定义 23 | win32{ 24 | CONFIG += debug_and_release 25 | CONFIG(release, debug|release) { 26 | target_path = ./build_/dist 27 | } else { 28 | target_path = ./build_/debug 29 | } 30 | DESTDIR = ../bin 31 | MOC_DIR = $$target_path/moc 32 | RCC_DIR = $$target_path/rcc 33 | OBJECTS_DIR = $$target_path/obj 34 | } 35 | 36 | unix{ 37 | CONFIG += debug_and_release 38 | CONFIG(release, debug|release) { 39 | target_path = ./build_/dist 40 | } else { 41 | target_path = ./build_/debug 42 | } 43 | DESTDIR = ../bin 44 | MOC_DIR = $$target_path/moc 45 | RCC_DIR = $$target_path/rcc 46 | OBJECTS_DIR = $$target_path/obj 47 | } 48 | 49 | # 打印信息 50 | message(Qt version: $$[QT_VERSION]) 51 | message(Qt is installed in $$[QT_INSTALL_PREFIX]) 52 | message(the NORM will create in folder: $$target_path) 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 数据库ORM组件说明 2 | 3 | 版本: V 1.0.4.0 4 | 作者: daodaoliang 5 | 时间: 2021年06月09日 6 | 7 | ### 0. 组件信息说明 8 | 9 | ![项目架构](./scripts/pro_arch.png) 10 | 11 | |基础信息|信息描述| 12 | |:------|:------| 13 | |当前开发版本|V 1.0.4.0| 14 | |代码仓库位置|https://github.com/daodaoliang/NORM| 15 | |开发语言|Qt c++| 16 | |支持的数据库|MySqlServer、SQLite、PostgreSQL、MSSqlServer、DM7| 17 | |开发者的开发环境|windows + Qt5.10 + mingw| 18 | 19 | ### 1. 组件使用说明 20 | 21 | #### 1.1 编译组件库 22 | 23 | * 获取源码; 24 | * 在QtCreator中打开NORM.pro, 不要选择影子构建,开始编译构建; 25 | * 同级目录中会出现bin文件夹,里面就是编译好的组件库, 注意看版本信息; 26 | * 在外部项目使用时直接把编译好的库和inc文件夹下的文件打包使用即可; 27 | * 在内部项目使用时,请直接引用NORM_inc.pri即可,同时需要手动把编译好的库拷贝到主程序执行目录,或者编写辅助脚本在运行前拷贝; 28 | 29 | #### 1.2 运行测试用例 30 | 31 | * 编译并运行 example 子项目即可,不要勾选在终端运行; 32 | 33 | #### 1.3 使用实例 34 | 35 | 暂时不想写,过几天再说o(╯□╰)o 36 | 37 | ### 2. 开发计划 38 | 39 | 暂无计划 40 | 41 | ### 3. 版本说明 42 | 43 | **当前最新版本 V 1.0.4.0** 44 | 45 | > V 1.0.4.0 46 | 47 | * 增加达梦数据库的支持 48 | 49 | > V 1.0.3.0 50 | 51 | * 增加QStringList的支持,解析成mysql中的 JSON数据字段; 52 | 53 | > V 1.0.2.0 54 | 55 | * 增加数据库的自动创建; 56 | * 解决metaobject中方法的编译错误; 57 | * 修改原项目的方法名字,解除它和员框架的依赖; 58 | 59 | > V 1.0.1.0 60 | 61 | * 在 QDjango 中迁移出 db 子项目; 62 | * 修正工程文件的编译依赖关系; 63 | * 增加第三方引用时的工程文件,并增加测试用例; 64 | 65 | ### 4. 联系我 66 | 67 | 有任何疑问请联系我: 68 | QQ: 88341189 69 | email: daodaoliang@yeah.net 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /NORM/src/NOrmModel.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "NOrm.h" 5 | #include "NOrmModel.h" 6 | #include "NOrmQuerySet.h" 7 | 8 | NOrmModel::NOrmModel(QObject *parent) 9 | : QObject(parent) 10 | { 11 | } 12 | 13 | QVariant NOrmModel::pk() const 14 | { 15 | const NOrmMetaModel metaModel = NOrm::metaModel(metaObject()->className()); 16 | return property(metaModel.primaryKey()); 17 | } 18 | 19 | void NOrmModel::setPk(const QVariant &pk) 20 | { 21 | const NOrmMetaModel metaModel = NOrm::metaModel(metaObject()->className()); 22 | setProperty(metaModel.primaryKey(), pk); 23 | } 24 | 25 | QObject *NOrmModel::foreignKey(const char *name) const 26 | { 27 | const NOrmMetaModel metaModel = NOrm::metaModel(metaObject()->className()); 28 | return metaModel.foreignKey(this, name); 29 | } 30 | 31 | void NOrmModel::setForeignKey(const char *name, QObject *value) 32 | { 33 | const NOrmMetaModel metaModel = NOrm::metaModel(metaObject()->className()); 34 | metaModel.setForeignKey(this, name, value); 35 | } 36 | 37 | bool NOrmModel::remove() 38 | { 39 | const NOrmMetaModel metaModel = NOrm::metaModel(metaObject()->className()); 40 | return metaModel.remove(this); 41 | } 42 | 43 | bool NOrmModel::save() 44 | { 45 | const NOrmMetaModel metaModel = NOrm::metaModel(metaObject()->className()); 46 | return metaModel.save(this); 47 | } 48 | 49 | /** Returns a string representation of the model instance. 50 | */ 51 | QString NOrmModel::toString() const 52 | { 53 | const NOrmMetaModel metaModel = NOrm::metaModel(metaObject()->className()); 54 | const QByteArray pkName = metaModel.primaryKey(); 55 | return QString::fromLatin1("%1(%2=%3)").arg(QString::fromLatin1(metaObject()->className()), QString::fromLatin1(pkName), property(pkName).toString()); 56 | } 57 | 58 | -------------------------------------------------------------------------------- /NORM/inc/NOrmWhere.h: -------------------------------------------------------------------------------- 1 | #ifndef NORM_WHERE_H 2 | #define NORM_WHERE_H 3 | 4 | #include 5 | #include 6 | 7 | #include "NOrm_p.h" 8 | 9 | class NOrmMetaModel; 10 | class NOrmQuery; 11 | class NOrmWherePrivate; 12 | 13 | class NOrmWhere 14 | { 15 | public: 16 | enum Operation 17 | { 18 | None, 19 | Equals, 20 | NotEquals, 21 | GreaterThan, 22 | LessThan, 23 | GreaterOrEquals, 24 | LessOrEquals, 25 | StartsWith, 26 | EndsWith, 27 | Contains, 28 | IsIn, 29 | IsNull, 30 | IEquals, 31 | INotEquals, 32 | IStartsWith, 33 | IEndsWith, 34 | IContains 35 | }; 36 | 37 | enum AggregateType{ 38 | AVG, 39 | COUNT, 40 | SUM, 41 | MIN, 42 | MAX 43 | }; 44 | NOrmWhere(); 45 | NOrmWhere(const NOrmWhere &other); 46 | NOrmWhere(const QString &key, NOrmWhere::Operation operation, QVariant value); 47 | ~NOrmWhere(); 48 | 49 | NOrmWhere& operator=(const NOrmWhere &other); 50 | NOrmWhere operator!() const; 51 | NOrmWhere operator&&(const NOrmWhere &other) const; 52 | NOrmWhere operator||(const NOrmWhere &other) const; 53 | 54 | void bindValues(NOrmQuery &query) const; 55 | bool isAll() const; 56 | bool isNone() const; 57 | QString sql(const QSqlDatabase &db) const; 58 | QString toString() const; 59 | 60 | private: 61 | QString sqlDefult(const QSqlDatabase &db) const; 62 | QString sqlMysql(const QSqlDatabase &db) const; 63 | QString sqlSqlite(const QSqlDatabase &db) const; 64 | QString sqlPostgre(const QSqlDatabase &db) const; 65 | QString sqlDaMeng(const QSqlDatabase &db) const; 66 | 67 | private: 68 | QSharedDataPointer d; 69 | friend class NOrmCompiler; 70 | }; 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /NORM/inc/NOrm_p.h: -------------------------------------------------------------------------------- 1 | #ifndef NORM_P_H 2 | #define NORM_P_H 3 | 4 | /* 5 | * 描述: NORM 私有接口 6 | * 作者: daodaoliang@yeah.net 7 | * 时间: 2021-07-16 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | /** 19 | * @brief The NOrmDatabase class 20 | * 链接数据库的操作集合 21 | */ 22 | class NOrmDatabase : public QObject 23 | { 24 | Q_OBJECT 25 | 26 | public: 27 | NOrmDatabase(QObject *parent = nullptr); 28 | 29 | /** 30 | * @brief The DatabaseType enum 数据库类型 31 | */ 32 | enum DatabaseType { 33 | // 未知数据库类型 34 | UnknownDB, 35 | // sqlserver 36 | MSSqlServer, 37 | // mysql 38 | MySqlServer, 39 | // pgsql 40 | PostgreSQL, 41 | // oracle 42 | Oracle, 43 | // sybase 44 | Sybase, 45 | // sqlite 46 | SQLite, 47 | // interbase 48 | Interbase, 49 | // db2 50 | DB2, 51 | // 达梦 52 | DaMeng 53 | }; 54 | 55 | /** 56 | * @brief databaseType 获取数据库类型 57 | * @param db 数据库信息 58 | * @return 数据库类型 59 | */ 60 | static DatabaseType databaseType(const QSqlDatabase &db); 61 | 62 | // 数据库对象 63 | QSqlDatabase reference; 64 | 65 | // 数据库锁 66 | QMutex mutex; 67 | 68 | // 线程 和 数据库链接的映射 69 | QMap copies; 70 | 71 | // 链接ID 72 | qint64 connectionId; 73 | 74 | private slots: 75 | 76 | // 线程结束 77 | void threadFinished(); 78 | }; 79 | 80 | /** 81 | * @brief The NOrmQuery class 数据库查询对象 82 | */ 83 | class NOrmQuery : public QSqlQuery 84 | { 85 | public: 86 | NOrmQuery(QSqlDatabase db); 87 | void addBindValue(const QVariant &val, QSql::ParamType paramType = QSql::In); 88 | bool exec(); 89 | bool exec(const QString &query); 90 | }; 91 | 92 | #endif 93 | -------------------------------------------------------------------------------- /Example/testmodel.cpp: -------------------------------------------------------------------------------- 1 | #include "testmodel.h" 2 | 3 | void TestTable::setTestFieldString(const QString &testFieldString) 4 | { 5 | mTestFieldString = testFieldString; 6 | } 7 | 8 | TestTable::TestTable(QObject *parent) 9 | : NOrmModel(parent) 10 | { 11 | } 12 | 13 | QString TestTable::testFieldString() const 14 | { 15 | return mTestFieldString; 16 | } 17 | 18 | bool TestTable::testFieldBool() const 19 | { 20 | return mTestFieldBool; 21 | } 22 | 23 | void TestTable::setTestFieldBool(bool testFieldBool) 24 | { 25 | mTestFieldBool = testFieldBool; 26 | } 27 | 28 | QDate TestTable::testFieldDate() const 29 | { 30 | return mTestFieldDate; 31 | } 32 | 33 | void TestTable::setTestFieldDate(const QDate &testFieldDate) 34 | { 35 | mTestFieldDate = testFieldDate; 36 | } 37 | 38 | QTime TestTable::testFieldTime() const 39 | { 40 | return mTestFieldTime; 41 | } 42 | 43 | void TestTable::setTestFieldTime(const QTime &testFieldTime) 44 | { 45 | mTestFieldTime = testFieldTime; 46 | } 47 | 48 | QDateTime TestTable::testFieldDateTime() const 49 | { 50 | return mTestFieldDateTime; 51 | } 52 | 53 | void TestTable::setTestFieldDateTime(const QDateTime &testFieldDateTime) 54 | { 55 | mTestFieldDateTime = testFieldDateTime; 56 | } 57 | 58 | QByteArray TestTable::testFieldByteArray() const 59 | { 60 | return mTestFieldByteArray; 61 | } 62 | 63 | void TestTable::setTestFieldByteArray(const QByteArray &testFieldByteArray) 64 | { 65 | mTestFieldByteArray = testFieldByteArray; 66 | } 67 | 68 | double TestTable::testFieldDouble() const 69 | { 70 | return mTestFieldDouble; 71 | } 72 | 73 | void TestTable::setTestFieldDouble(double testFieldDouble) 74 | { 75 | mTestFieldDouble = testFieldDouble; 76 | } 77 | 78 | quint32 TestTable::testFieldInt() const 79 | { 80 | return mTestFieldInt; 81 | } 82 | 83 | void TestTable::setTestFieldInt(const quint32 &testFieldInt) 84 | { 85 | mTestFieldInt = testFieldInt; 86 | } 87 | 88 | QStringList TestTable::testStringList() const 89 | { 90 | return mTestStringList; 91 | } 92 | 93 | void TestTable::setTestStringList(const QStringList &testStringList) 94 | { 95 | mTestStringList = testStringList; 96 | } 97 | -------------------------------------------------------------------------------- /NORM/inc/NOrm.h: -------------------------------------------------------------------------------- 1 | #ifndef NORM_H 2 | #define NORM_H 3 | 4 | /* 5 | * 描述: NORM api接口 6 | * 作者: daodaoliang@yeah.net 7 | * 时间: 2021-07-16 8 | */ 9 | 10 | #include "NOrmMetaModel.h" 11 | #include 12 | 13 | class QObject; 14 | class QSqlDatabase; 15 | class QSqlQuery; 16 | class QString; 17 | 18 | // 前置声明 19 | extern QMap globalMetaModels; 20 | 21 | /** 22 | * @brief The NOrm class 23 | * NORM的接口 24 | */ 25 | class NOrm 26 | { 27 | public: 28 | /** 29 | * @brief createTables 创建数据表 30 | * @return 创建操作的结果 31 | */ 32 | static bool createTables(); 33 | 34 | /** 35 | * @brief dropTables 删除数据表 36 | * @return 删除操作的结果 37 | */ 38 | static bool dropTables(); 39 | 40 | /** 41 | * @brief database 当前链接的数据库 42 | * @return 数据库信息 43 | */ 44 | static QSqlDatabase database(); 45 | 46 | /** 47 | * @brief setDatabase 设置当前需要连接的数据库 48 | * @param database 数据库信息 49 | * @return 设置操作结果 50 | */ 51 | static bool setDatabase(QSqlDatabase database); 52 | 53 | /** 54 | * @brief isDebugEnabled 是否是调试模式 55 | * @return true or false 56 | */ 57 | static bool isDebugEnabled(); 58 | 59 | /** 60 | * @brief setDebugEnabled 设置组件是否运行在调试模式 61 | * @param enabled true or false 62 | */ 63 | static void setDebugEnabled(bool enabled); 64 | 65 | template 66 | /** 67 | * @brief registerModel 注册模型(模板) 68 | * @return 模型结构体 69 | */ 70 | static NOrmMetaModel registerModel(); 71 | 72 | /** 73 | * @brief metaModel 模型数据 74 | * @param name 模型的名字 75 | * @return 封装好的模型对象 76 | */ 77 | static NOrmMetaModel metaModel(const char *name); 78 | static QStack metaModels(); 79 | 80 | private: 81 | /** 82 | * @brief registerModel 注册模型 83 | * @param meta 模型对象 84 | * @return 封装好的模型对象 85 | */ 86 | static NOrmMetaModel registerModel(const QMetaObject *meta); 87 | 88 | friend class NOrmCompiler; 89 | friend class NOrmModel; 90 | friend class NOrmMetaModel; 91 | friend class NOrmSetPrivate; 92 | }; 93 | 94 | template 95 | NOrmMetaModel NOrm::registerModel() 96 | { 97 | return registerModel(&T::staticMetaObject); 98 | } 99 | 100 | #endif 101 | -------------------------------------------------------------------------------- /docs/_res/copy_code.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * docsify-copy-code 3 | * v2.0.2 4 | * https://github.com/jperasmus/docsify-copy-code 5 | * (c) 2018 JP Erasmus 6 | * MIT license 7 | */ 8 | !function(){"use strict";!function(e,o){void 0===o&&(o={});var t=o.insertAt;if(e&&"undefined"!=typeof document){var n=document.head||document.getElementsByTagName("head")[0],c=document.createElement("style");c.type="text/css","top"===t&&n.firstChild?n.insertBefore(c,n.firstChild):n.appendChild(c),c.styleSheet?c.styleSheet.cssText=e:c.appendChild(document.createTextNode(e))}}('.docsify-copy-code-button{z-index:1;right:0;padding:10px;opacity:0;border:0;border-radius:0;outline:0;cursor:pointer}.docsify-copy-code-button,.docsify-copy-code-button:after{position:absolute;top:0;background:#ccc;color:#fff;transition:all .25s ease}.docsify-copy-code-button:after{content:"Copied!";z-index:0;right:100%;margin:5px 10px 0;padding:5px;border-radius:3px;font-size:11px;-webkit-transform:translateX(120%) scale(0);transform:translateX(120%) scale(0)}.docsify-copy-code-button.success:after{-webkit-transform:translateX(0) scale(1);transform:translateX(0) scale(1)}pre[v-pre]:hover .docsify-copy-code-button{opacity:1}'),document.querySelector('link[href*="docsify-copy-code"]')&&console.warn("[Deprecation] Link to external docsify-copy-code stylesheet is no longer necessary."),window.DocsifyCopyCodePlugin={init:function(){return function(e,o){e.ready(function(){console.warn("[Deprecation] Manually initializing docsify-copy-code using window.DocsifyCopyCodePlugin.init() is no longer necessary.")})}}},window.$docsify=window.$docsify||{},window.$docsify.plugins=[function(e,o){e.doneEach(function(){Array.apply(null,document.querySelectorAll("pre[v-pre]")).forEach(function(e,t,n){var c=document.createElement("button");c.appendChild(document.createTextNode("Click to copy")),c.classList.add("docsify-copy-code-button"),o.config.themeColor&&(c.style.background=o.config.themeColor),c.addEventListener("click",function(o){var t=document.createRange(),n=e.querySelector("code"),i=window.getSelection();t.selectNode(n),i.removeAllRanges(),i.addRange(t);try{document.execCommand("copy")&&(c.classList.add("success"),setTimeout(function(){c.classList.remove("success")},1e3))}catch(e){c.classList.add("error"),setTimeout(function(){c.classList.remove("error")},1e3)}"function"==typeof(i=window.getSelection()).removeRange?i.removeRange(t):"function"==typeof i.removeAllRanges&&i.removeAllRanges()}),e.appendChild(c)})})}].concat(window.$docsify.plugins||[])}(); 9 | //# sourceMappingURL=docsify-copy-code.min.js.map -------------------------------------------------------------------------------- /Example/testmodel.h: -------------------------------------------------------------------------------- 1 | #ifndef TESTMODEL_H 2 | #define TESTMODEL_H 3 | /** 4 | * 作者: daodaoliang 5 | * 时间: 2018年7月5日 6 | * 版本: 1.0.1.0 7 | * 邮箱: daodaoliang@yeah.net 8 | */ 9 | 10 | #include 11 | #include "NOrmModel.h" 12 | #include 13 | #include 14 | 15 | class TestTable: public NOrmModel { 16 | Q_OBJECT 17 | Q_PROPERTY(QString testFieldString READ testFieldString WRITE setTestFieldString) 18 | Q_PROPERTY(bool testFieldBool READ testFieldBool WRITE setTestFieldBool) 19 | Q_PROPERTY(QDate testFieldDate READ testFieldDate WRITE setTestFieldDate) 20 | Q_PROPERTY(QTime testFieldTime READ testFieldTime WRITE setTestFieldTime) 21 | Q_PROPERTY(QDateTime testFieldDateTime READ testFieldDateTime WRITE setTestFieldDateTime) 22 | Q_PROPERTY(QByteArray testFieldByteArray READ testFieldByteArray WRITE setTestFieldByteArray) 23 | Q_PROPERTY(double testFieldDouble READ testFieldDouble WRITE setTestFieldDouble) 24 | Q_PROPERTY(int testFieldInt READ testFieldInt WRITE setTestFieldInt) 25 | Q_PROPERTY(QStringList testStringList READ testStringList WRITE setTestStringList) 26 | Q_CLASSINFO("testFieldString","primary_key=true max_length=45") 27 | 28 | public: 29 | TestTable(QObject *parent=nullptr); 30 | 31 | public: 32 | /***************** setter && getter ********************/ 33 | QString testFieldString() const; 34 | void setTestFieldString(const QString &testFieldString); 35 | 36 | bool testFieldBool() const; 37 | void setTestFieldBool(bool testFieldBool); 38 | 39 | QDate testFieldDate() const; 40 | void setTestFieldDate(const QDate &testFieldDate); 41 | 42 | QTime testFieldTime() const; 43 | void setTestFieldTime(const QTime &testFieldTime); 44 | 45 | QDateTime testFieldDateTime() const; 46 | void setTestFieldDateTime(const QDateTime &testFieldDateTime); 47 | 48 | QByteArray testFieldByteArray() const; 49 | void setTestFieldByteArray(const QByteArray &testFieldByteArray); 50 | 51 | double testFieldDouble() const; 52 | void setTestFieldDouble(double testFieldDouble); 53 | 54 | quint32 testFieldInt() const; 55 | void setTestFieldInt(const quint32 &testFieldInt); 56 | 57 | QStringList testStringList() const; 58 | void setTestStringList(const QStringList &testStringList); 59 | 60 | private: 61 | // string 类型的字段 62 | QString mTestFieldString; 63 | // bool 类型的字段 64 | bool mTestFieldBool; 65 | // date 类型的字段 66 | QDate mTestFieldDate; 67 | // time 类型的字段 68 | QTime mTestFieldTime; 69 | // datetime 类型的字段 70 | QDateTime mTestFieldDateTime; 71 | // ByteArray 类型的字段 72 | QByteArray mTestFieldByteArray; 73 | // Double 类型的字段 74 | double mTestFieldDouble; 75 | // int 类型的字段 76 | int mTestFieldInt; 77 | // stringlist 类型的字段 78 | QStringList mTestStringList; 79 | }; 80 | 81 | 82 | #endif // TESTMODEL_H 83 | -------------------------------------------------------------------------------- /NORM/inc/NOrmQuerySet_p.h: -------------------------------------------------------------------------------- 1 | #ifndef NORM_QUERYSET_P_H 2 | #define NORM_QUERYSET_P_H 3 | 4 | #include 5 | #include "NOrm_p.h" 6 | #include "NOrmWhere.h" 7 | 8 | class NOrmMetaModel; 9 | 10 | class NOrmModelReference 11 | { 12 | public: 13 | NOrmModelReference(const QString &tableReference_ = QString(), const NOrmMetaModel &metaModel_ = NOrmMetaModel(), bool nullable_ = false) 14 | : tableReference(tableReference_) 15 | , metaModel(metaModel_) 16 | , nullable(nullable_) 17 | { 18 | }; 19 | 20 | QString tableReference; 21 | NOrmMetaModel metaModel; 22 | bool nullable; 23 | }; 24 | 25 | class NOrmReverseReference 26 | { 27 | public: 28 | QString leftHandKey; 29 | QString rightHandKey; 30 | }; 31 | 32 | /** \internal 33 | */ 34 | class NOrmCompiler 35 | { 36 | public: 37 | NOrmCompiler(const char *modelName, const QSqlDatabase &db); 38 | QString fromSql(); 39 | QStringList fieldNames(bool recurse, const QStringList *fields = nullptr, NOrmMetaModel *metaModel = nullptr, const QString &modelPath = QString(), bool nullable = false); 40 | QString orderLimitSql(const QStringList &orderBy, int lowMark, int highMark); 41 | void resolve(NOrmWhere &where); 42 | 43 | private: 44 | QString databaseColumn(const QString &name); 45 | QString referenceModel(const QString &modelPath, NOrmMetaModel *metaModel, bool nullable); 46 | void limitSql(QString &limit, int lowMark, int highMark); 47 | 48 | QSqlDriver *driver; 49 | NOrmMetaModel baseModel; 50 | QMap modelRefs; 51 | QMap reverseModelRefs; 52 | QMap fieldColumnCache; 53 | }; 54 | 55 | /** \internal 56 | */ 57 | class NOrmQuerySetPrivate 58 | { 59 | public: 60 | NOrmQuerySetPrivate(const char *modelName); 61 | 62 | void addFilter(const NOrmWhere &where); 63 | NOrmWhere resolvedWhere(const QSqlDatabase &db) const; 64 | bool sqlDelete(); 65 | bool sqlFetch(); 66 | bool sqlInsert(const QVariantMap &fields, QVariant *insertId = nullptr); 67 | bool sqlLoad(QObject *model, int index); 68 | int sqlUpdate(const QVariantMap &fields); 69 | QList sqlValues(const QStringList &fields); 70 | QList sqlValuesList(const QStringList &fields); 71 | 72 | // SQL queries 73 | NOrmQuery aggregateQuery(const NOrmWhere::AggregateType func, const QString &field) const; 74 | NOrmQuery deleteQuery() const; 75 | NOrmQuery insertQuery(const QVariantMap &fields) const; 76 | NOrmQuery selectQuery() const; 77 | NOrmQuery updateQuery(const QVariantMap &fields) const; 78 | 79 | // reference counter 80 | QAtomicInt counter; 81 | 82 | bool hasResults; 83 | int lowMark; 84 | int highMark; 85 | NOrmWhere whereClause; 86 | QStringList orderBy; 87 | QList properties; 88 | bool selectRelated; 89 | QStringList relatedFields; 90 | 91 | private: 92 | Q_DISABLE_COPY(NOrmQuerySetPrivate) 93 | QByteArray m_modelName; 94 | friend class NOrmMetaModel; 95 | }; 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /NORM/inc/NOrmMetaModel.h: -------------------------------------------------------------------------------- 1 | #ifndef NORMMETAMODEL_H 2 | #define NORMMETAMODEL_H 3 | 4 | /* 5 | * 描述: NORM 数据表元模型 6 | * 作者: daodaoliang@yeah.net 7 | * 时间: 2021-07-16 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "NOrm_p.h" 15 | 16 | class NOrmMetaFieldPrivate; 17 | class NOrmMetaModelPrivate; 18 | 19 | /** 20 | * @brief The NOrmMetaField class 数据表字段抽象 21 | */ 22 | class NOrmMetaField 23 | { 24 | public: 25 | 26 | // 构造 27 | NOrmMetaField(); 28 | NOrmMetaField(const NOrmMetaField &other); 29 | 30 | // 析构 31 | ~NOrmMetaField(); 32 | 33 | // 重载复制 34 | NOrmMetaField& operator=(const NOrmMetaField &other); 35 | 36 | // 列名 37 | QString column() const; 38 | 39 | // 是否自增 40 | bool isAutoIncrement() const; 41 | 42 | // 是否空 43 | bool isBlank() const; 44 | 45 | // 是否允许空值 46 | bool isNullable() const; 47 | 48 | // 是否唯一 49 | bool isUnique() const; 50 | 51 | // 是否有效 52 | bool isValid() const; 53 | 54 | // 列名 55 | QString name() const; 56 | 57 | // 最大长度 58 | int maxLength() const; 59 | 60 | // 转换成数据库中将要存储的数据值 61 | QVariant toDatabase(const QVariant &value) const; 62 | 63 | private: 64 | // 私有功能对象 65 | QSharedDataPointer d; 66 | 67 | // 属于哪个元对象表 68 | friend class NOrmMetaModel; 69 | }; 70 | 71 | /** 72 | * @brief The NOrmMetaModel class 表元对象模型 73 | */ 74 | class NOrmMetaModel 75 | { 76 | public: 77 | // 构造 78 | NOrmMetaModel(const QMetaObject *model = nullptr); 79 | NOrmMetaModel(const NOrmMetaModel &other); 80 | 81 | // 析构 82 | ~NOrmMetaModel(); 83 | 84 | // 重载赋值 85 | NOrmMetaModel& operator=(const NOrmMetaModel &other); 86 | 87 | // 是否有效 88 | bool isValid() const; 89 | 90 | // 创建数据表 91 | bool createTable() const; 92 | 93 | // 建表语句 94 | QStringList createTableSql() const; 95 | 96 | // 删除数据表 97 | bool dropTable() const; 98 | 99 | void load(QObject *model, const QVariantList &props, int &pos, const QStringList &relatedFields = QStringList()) const; 100 | 101 | // 删除数据记录 102 | bool remove(QObject *model) const; 103 | 104 | // 插入数据记录 105 | bool save(QObject *model) const; 106 | 107 | // 外键 108 | QObject *foreignKey(const QObject *model, const char *name) const; 109 | void setForeignKey(QObject *model, const char *name, QObject *value) const; 110 | 111 | // 类名字 112 | QString className() const; 113 | 114 | // 本表字段信息 115 | NOrmMetaField localField(const char *name) const; 116 | 117 | // 本表字段信息 118 | QList localFields() const; 119 | 120 | // 外键信息 121 | QMap foreignFields() const; 122 | 123 | // 主键信息 124 | QByteArray primaryKey() const; 125 | 126 | // 表名字 127 | QString table() const; 128 | 129 | private: 130 | QString getBoolType(NOrmDatabase::DatabaseType databaseType) const; 131 | QString getByteArrayType(NOrmDatabase::DatabaseType databaseType, int maxLength) const; 132 | QString getDateType(NOrmDatabase::DatabaseType databaseType) const; 133 | QString getDateTimeType(NOrmDatabase::DatabaseType databaseType) const; 134 | QString getDoubleType(NOrmDatabase::DatabaseType databaseType) const; 135 | QString getIntType(NOrmDatabase::DatabaseType databaseType) const; 136 | QString getLongLongType(NOrmDatabase::DatabaseType databaseType) const; 137 | QString getStringType(NOrmDatabase::DatabaseType databaseType, int maxLength) const; 138 | QString getTimeType(NOrmDatabase::DatabaseType databaseType) const; 139 | QString getStringListType(NOrmDatabase::DatabaseType databaseType, int maxLength) const; 140 | QString attributeNull(NOrmDatabase::DatabaseType databaseType) const; 141 | QString attributeUnique(NOrmDatabase::DatabaseType databaseType) const; 142 | QString attributePrimaryKey(NOrmDatabase::DatabaseType databaseType) const; 143 | QString attributeAutoIncrement(NOrmDatabase::DatabaseType databaseType, const NOrmMetaField &field) const; 144 | 145 | private: 146 | QSharedDataPointer d; 147 | }; 148 | 149 | #endif 150 | -------------------------------------------------------------------------------- /Example/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "NOrm.h" 3 | #include 4 | #include 5 | #include 6 | #include "testmodel.h" 7 | #include "NOrmQuerySet.h" 8 | #include 9 | #include 10 | #include 11 | 12 | bool initTestEnv() { 13 | // 数据库基本信息 14 | QString db_host = "127.0.0.1"; 15 | QString db_user = "root"; 16 | QString db_pwd = "sduept"; 17 | QString db_name = "test"; 18 | qint32 db_port = 3306; 19 | 20 | // 设置调试模式(调试模式下会进行详细的日志输出) 21 | NOrm::setDebugEnabled(true); 22 | 23 | // 设置数据库的初始信息(数据库链接名字用来唯一识别数据库链接) 24 | QSqlDatabase db = QSqlDatabase::addDatabase("QMYSQL", "test_"); 25 | db.setHostName(db_host); 26 | db.setPassword(db_pwd); 27 | db.setPort(db_port); 28 | db.setUserName(db_user); 29 | db.setDatabaseName(db_name); 30 | bool ret = NOrm::setDatabase(db); 31 | if (NOrm::isDebugEnabled()) { 32 | qDebug() << QObject::tr("数据库设置:%1").arg(ret ? "成功" : "失败"); 33 | } 34 | return ret; 35 | } 36 | 37 | int main(int argc, char *argv[]) { 38 | QCoreApplication a(argc, argv); 39 | 40 | qDebug() << "************************测试用例开始**********************************"; 41 | QTime mCountTime; 42 | mCountTime.start(); 43 | 44 | // workflow 001 --> 初始化测试环境 45 | bool ret = initTestEnv(); 46 | qDebug() << QObject::tr("测试环境初始化:%1").arg(ret ? "成功" : "失败"); 47 | if (!ret) { 48 | return -1; 49 | } 50 | 51 | // workflow 002 --> 注册模型 52 | NOrm::registerModel(); 53 | 54 | // workflow 004 --> 初始化数据表(获取数据表数据表存在则进行删除) 55 | QStringList tableList = NOrm::database().tables(); 56 | if (tableList.contains("testtable")) { 57 | NOrm::dropTables(); 58 | } 59 | ret = NOrm::createTables(); 60 | qDebug() << QObject::tr("创建数据库表结构:%1").arg(ret ? "成功" : "失败"); 61 | if (!ret) { 62 | return -1; 63 | } 64 | 65 | // workflow 005 --> 数据增加测试(100条数据) 66 | NOrm::database().transaction(); 67 | for (int index = 0; index != 100; ++index) { 68 | TestTable test_case; 69 | test_case.setTestFieldBool(true); 70 | test_case.setTestFieldByteArray(QByteArray(10, 'a')); 71 | test_case.setTestFieldDate(QDate::currentDate()); 72 | test_case.setTestFieldDateTime(QDateTime::currentDateTime()); 73 | test_case.setTestFieldDouble(12.345678); 74 | test_case.setTestFieldString(QUuid::createUuid().toString().replace("{", "").replace("}", "")); 75 | test_case.setTestFieldTime(QTime::currentTime()); 76 | test_case.setTestFieldInt(static_cast(index)); 77 | test_case.setTestStringList(QStringList() << "123" << "234"); 78 | test_case.save(); 79 | qDebug() << "增加了一条新的数据记录"; 80 | } 81 | NOrm::database().commit(); 82 | 83 | // 数据查询测试 --> 0061 count 数据条数 84 | NOrmQuerySet testTables_case; 85 | qDebug() << QObject::tr("当前数据库共有数据:%1条").arg(testTables_case.count()); 86 | 87 | // 数据查询测试 --> 0062 单where 查询单条数据 88 | NOrmQuerySet queryMany; 89 | TestTable* rets_single = queryMany.get(NOrmWhere("testFieldBool", NOrmWhere::GreaterOrEquals, true)); 90 | 91 | if(rets_single){ 92 | // 打印测试 93 | qDebug() << QObject::tr("查询数据的结果主键:%1").arg(rets_single->pk().toString()); 94 | } 95 | 96 | // 数据查询测试 --> 0063 多 where查询 97 | NOrmQuerySet rets = queryMany.filter(NOrmWhere("testFieldInt", NOrmWhere::GreaterOrEquals, 1) && NOrmWhere("testFieldInt", NOrmWhere::LessOrEquals, 5)); 98 | for (int tmpIndex = 0; tmpIndex != rets.size(); ++tmpIndex) { 99 | // 读取这一行数据 100 | auto rowData = rets.at(tmpIndex); 101 | 102 | // 打印测试 103 | qDebug() << QObject::tr("查询数据的结果主键:%1").arg(rowData->pk().toString()); 104 | 105 | // 内存释放 106 | delete rowData; 107 | } 108 | 109 | // 数据查询测试 --> 0064 多where查询 110 | rets = queryMany.filter(NOrmWhere("testFieldInt", NOrmWhere::GreaterOrEquals, 4) || NOrmWhere("testFieldInt", NOrmWhere::LessOrEquals, 2)); 111 | for (int tmpIndex = 0; tmpIndex != rets.size(); ++tmpIndex) { 112 | // 读取这一行数据 113 | auto rowData = rets.at(tmpIndex); 114 | 115 | // 打印测试 116 | qDebug() << QObject::tr("查询数据的结果主键:%1").arg(rowData->pk().toString()); 117 | 118 | // 内存释放 119 | delete rowData; 120 | } 121 | 122 | qDebug() << "************************测试用例结束**********************************"; 123 | qDebug() << " 测试用例花费时间:" << mCountTime.elapsed() << " 毫秒"; 124 | return 0; 125 | } 126 | -------------------------------------------------------------------------------- /docs/_res/doc-pagination.min.js: -------------------------------------------------------------------------------- 1 | !function(n,t){"object"==typeof exports&&"undefined"!=typeof module?t():"function"==typeof define&&define.amd?define(t):t()}(0,function(){"use strict";var n,o=(function(n,t){function e(n,t){return t.querySelector(n)}(t=n.exports=function(n,t){return e(n,t=t||document)}).all=function(n,t){return(t=t||document).querySelectorAll(n)},t.engine=function(n){if(!n.one)throw new Error(".one callback required");if(!n.all)throw new Error(".all callback required");return e=n.one,t.all=n.all,t}}(n={exports:{}},n.exports),n.exports);o.all,o.engine;try{var a=o}catch(n){a=o}var t=Element.prototype,r=t.matches||t.webkitMatchesSelector||t.mozMatchesSelector||t.msMatchesSelector||t.oMatchesSelector,l=function(n,t){if(!n||1!==n.nodeType)return!1;if(r)return r.call(n,t);for(var e=a.all(t,n.parentNode),i=0;i*{line-height:1;vertical-align:middle}.pagination-item-label svg{height:.8em;width:auto;stroke:currentColor;stroke-linecap:round;stroke-linejoin:round;stroke-width:1px}.pagination-item--next{margin-left:auto;text-align:right}.pagination-item--next svg{margin-left:.5em}.pagination-item--previous svg{margin-right:.5em}.pagination-item-title{font-size:1.6em}");var e=function(){function i(n,t){for(var e=0;e'},v=function(n,t){return[n.prev&&'\n \n ",n.next&&'\n \n "].filter(Boolean).join("")};window.$docsify=window.$docsify||{},window.$docsify.plugins=[function(n,t){var e=s({},p,t.config.pagination||{});function i(){var n=o("."+u);n&&(n.innerHTML=v(function(n){try{var e=n.route.path,t=f(o.all(".sidebar li a")).filter(function(n){return!l(n,".section-link")}).find(g(e)),i=f((c(t,"ul")||{}).children).filter(function(n){return"LI"===n.tagName.toUpperCase()}),a=i.findIndex(function(n){var t=d(n);return t&&g(e,t)});return{prev:new m(i[a-1]).toJSON(),next:new m(i[a+1]).toJSON()}}catch(n){return{}}}(t),e))}n.afterEach(function(n){return n+h()}),n.doneEach(function(){return i()})}].concat(window.$docsify.plugins||[])}); -------------------------------------------------------------------------------- /docs/_res/zoom-image.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | /* 3 | * medium-zoom v0.4.0 4 | * Medium zoom on your images in vanilla JavaScript 5 | * Copyright 2018 Francois Chalifour 6 | * https://github.com/francoischalifour/medium-zoom 7 | * MIT License 8 | */var _extends=Object.assign||function(a){for(var b,c=1;cw.scrollOffset&&o(150);}},u=function(a){-1 code[class*="language-"], 59 | pre[class*="language-"] { 60 | background: #f5f2f0; 61 | } 62 | 63 | /* Inline code */ 64 | :not(pre) > code[class*="language-"] { 65 | padding: .1em; 66 | border-radius: .3em; 67 | white-space: normal; 68 | } 69 | 70 | .token.comment, 71 | .token.prolog, 72 | .token.doctype, 73 | .token.cdata { 74 | color: slategray; 75 | } 76 | 77 | .token.punctuation { 78 | color: #999; 79 | } 80 | 81 | .namespace { 82 | opacity: .7; 83 | } 84 | 85 | .token.property, 86 | .token.tag, 87 | .token.boolean, 88 | .token.number, 89 | .token.constant, 90 | .token.symbol, 91 | .token.deleted { 92 | color: #905; 93 | } 94 | 95 | .token.selector, 96 | .token.attr-name, 97 | .token.string, 98 | .token.char, 99 | .token.builtin, 100 | .token.inserted { 101 | color: #690; 102 | } 103 | 104 | .token.operator, 105 | .token.entity, 106 | .token.url, 107 | .language-css .token.string, 108 | .style .token.string { 109 | color: #9a6e3a; 110 | background: hsla(0, 0%, 100%, .5); 111 | } 112 | 113 | .token.atrule, 114 | .token.attr-value, 115 | .token.keyword { 116 | color: #07a; 117 | } 118 | 119 | .token.function, 120 | .token.class-name { 121 | color: #DD4A68; 122 | } 123 | 124 | .token.regex, 125 | .token.important, 126 | .token.variable { 127 | color: #e90; 128 | } 129 | 130 | .token.important, 131 | .token.bold { 132 | font-weight: bold; 133 | } 134 | .token.italic { 135 | font-style: italic; 136 | } 137 | 138 | .token.entity { 139 | cursor: help; 140 | } 141 | 142 | pre[data-line] { 143 | position: relative; 144 | padding: 1em 0 1em 3em; 145 | } 146 | 147 | .line-highlight { 148 | position: absolute; 149 | left: 0; 150 | right: 0; 151 | padding: inherit 0; 152 | margin-top: 1em; /* Same as .prism’s padding-top */ 153 | 154 | background: hsla(24, 20%, 50%,.08); 155 | background: linear-gradient(to right, hsla(24, 20%, 50%,.1) 70%, hsla(24, 20%, 50%,0)); 156 | 157 | pointer-events: none; 158 | 159 | line-height: inherit; 160 | white-space: pre; 161 | } 162 | 163 | .line-highlight:before, 164 | .line-highlight[data-end]:after { 165 | content: attr(data-start); 166 | position: absolute; 167 | top: .4em; 168 | left: .6em; 169 | min-width: 1em; 170 | padding: 0 .5em; 171 | background-color: hsla(24, 20%, 50%,.4); 172 | color: hsl(24, 20%, 95%); 173 | font: bold 65%/1.5 sans-serif; 174 | text-align: center; 175 | vertical-align: .3em; 176 | border-radius: 999px; 177 | text-shadow: none; 178 | box-shadow: 0 1px white; 179 | } 180 | 181 | .line-highlight[data-end]:after { 182 | content: attr(data-end); 183 | top: auto; 184 | bottom: .4em; 185 | } 186 | 187 | .line-numbers .line-highlight:before, 188 | .line-numbers .line-highlight:after { 189 | content: none; 190 | } 191 | 192 | pre[class*="language-"].line-numbers { 193 | position: relative; 194 | padding-left: 3.8em; 195 | counter-reset: linenumber; 196 | } 197 | 198 | pre[class*="language-"].line-numbers > code { 199 | position: relative; 200 | white-space: inherit; 201 | } 202 | 203 | .line-numbers .line-numbers-rows { 204 | position: absolute; 205 | pointer-events: none; 206 | top: 0; 207 | font-size: 100%; 208 | left: -3.8em; 209 | width: 3em; /* works for line-numbers below 1000 lines */ 210 | letter-spacing: -1px; 211 | border-right: 1px solid #999; 212 | 213 | -webkit-user-select: none; 214 | -moz-user-select: none; 215 | -ms-user-select: none; 216 | user-select: none; 217 | 218 | } 219 | 220 | .line-numbers-rows > span { 221 | pointer-events: none; 222 | display: block; 223 | counter-increment: linenumber; 224 | } 225 | 226 | .line-numbers-rows > span:before { 227 | content: counter(linenumber); 228 | color: #999; 229 | display: block; 230 | padding-right: 0.8em; 231 | text-align: right; 232 | } 233 | 234 | .token.tab:not(:empty), 235 | .token.cr, 236 | .token.lf, 237 | .token.space { 238 | position: relative; 239 | } 240 | 241 | .token.tab:not(:empty):before, 242 | .token.cr:before, 243 | .token.lf:before, 244 | .token.space:before { 245 | color: hsl(24, 20%, 85%); 246 | position: absolute; 247 | } 248 | 249 | .token.tab:not(:empty):before { 250 | content: '\21E5'; 251 | } 252 | 253 | .token.cr:before { 254 | content: '\240D'; 255 | } 256 | 257 | .token.crlf:before { 258 | content: '\240D\240A'; 259 | } 260 | .token.lf:before { 261 | content: '\240A'; 262 | } 263 | 264 | .token.space:before { 265 | content: '\00B7'; 266 | } 267 | div.code-toolbar { 268 | position: relative; 269 | } 270 | 271 | div.code-toolbar > .toolbar { 272 | position: absolute; 273 | top: .3em; 274 | right: .2em; 275 | transition: opacity 0.3s ease-in-out; 276 | opacity: 0; 277 | } 278 | 279 | div.code-toolbar:hover > .toolbar { 280 | opacity: 1; 281 | } 282 | 283 | div.code-toolbar > .toolbar .toolbar-item { 284 | display: inline-block; 285 | } 286 | 287 | div.code-toolbar > .toolbar a { 288 | cursor: pointer; 289 | } 290 | 291 | div.code-toolbar > .toolbar button { 292 | background: none; 293 | border: 0; 294 | color: inherit; 295 | font: inherit; 296 | line-height: normal; 297 | overflow: visible; 298 | padding: 0; 299 | -webkit-user-select: none; /* for button */ 300 | -moz-user-select: none; 301 | -ms-user-select: none; 302 | } 303 | 304 | div.code-toolbar > .toolbar a, 305 | div.code-toolbar > .toolbar button, 306 | div.code-toolbar > .toolbar span { 307 | color: #bbb; 308 | font-size: .8em; 309 | padding: 0 .5em; 310 | background: #f5f2f0; 311 | background: rgba(224, 224, 224, 0.2); 312 | box-shadow: 0 2px 0 0 rgba(0,0,0,0.2); 313 | border-radius: .5em; 314 | } 315 | 316 | div.code-toolbar > .toolbar a:hover, 317 | div.code-toolbar > .toolbar a:focus, 318 | div.code-toolbar > .toolbar button:hover, 319 | div.code-toolbar > .toolbar button:focus, 320 | div.code-toolbar > .toolbar span:hover, 321 | div.code-toolbar > .toolbar span:focus { 322 | color: inherit; 323 | text-decoration: none; 324 | } 325 | 326 | .command-line-prompt { 327 | border-right: 1px solid #999; 328 | display: block; 329 | float: left; 330 | font-size: 100%; 331 | letter-spacing: -1px; 332 | margin-right: 1em; 333 | pointer-events: none; 334 | 335 | -webkit-user-select: none; 336 | -moz-user-select: none; 337 | -ms-user-select: none; 338 | user-select: none; 339 | } 340 | 341 | .command-line-prompt > span:before { 342 | color: #999; 343 | content: ' '; 344 | display: block; 345 | padding-right: 0.8em; 346 | } 347 | 348 | .command-line-prompt > span[data-user]:before { 349 | content: "[" attr(data-user) "@" attr(data-host) "] $"; 350 | } 351 | 352 | .command-line-prompt > span[data-user="root"]:before { 353 | content: "[" attr(data-user) "@" attr(data-host) "] #"; 354 | } 355 | 356 | .command-line-prompt > span[data-prompt]:before { 357 | content: attr(data-prompt); 358 | } 359 | 360 | -------------------------------------------------------------------------------- /NORM/src/NOrm.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "NOrm.h" 11 | 12 | // 链接前缀 13 | static const char *connectionPrefix = "_norm_"; 14 | 15 | // 对象映射 16 | QMap globalMetaModels = QMap(); 17 | 18 | // 数据库对象 19 | static NOrmDatabase *globalDatabase = nullptr; 20 | 21 | // 数据库类型 22 | static NOrmDatabase::DatabaseType globalDatabaseType = NOrmDatabase::UnknownDB; 23 | 24 | // 调试模式 25 | static bool globalDebugEnabled = false; 26 | 27 | NOrmDatabase::NOrmDatabase(QObject *parent) : QObject(parent), connectionId(0) 28 | { 29 | } 30 | 31 | void NOrmDatabase::threadFinished() 32 | { 33 | QThread *thread = qobject_cast(sender()); 34 | if (!thread) 35 | return; 36 | 37 | // 清理线程中的数据库链接 38 | QMutexLocker locker(&mutex); 39 | 40 | // 线程结束时 释放 数据库资源 41 | disconnect(thread, SIGNAL(finished()), this, SLOT(threadFinished())); 42 | 43 | // 获取数据库链接名字 44 | const QString connectionName = copies.value(thread).connectionName(); 45 | 46 | // 移除映射表 47 | copies.remove(thread); 48 | 49 | // 数据库链接移除 50 | if (connectionName.startsWith(QLatin1String(connectionPrefix))) 51 | QSqlDatabase::removeDatabase(connectionName); 52 | } 53 | 54 | static void closeDatabase() 55 | { 56 | delete globalDatabase; 57 | globalDatabase = nullptr; 58 | } 59 | 60 | static NOrmDatabase::DatabaseType getDatabaseType(QSqlDatabase &db) 61 | { 62 | const QString driverName = db.driverName(); 63 | if (driverName == QLatin1String("QMYSQL") || driverName == QLatin1String("QMYSQL3")) { 64 | return NOrmDatabase::MySqlServer; 65 | } else if (driverName == QLatin1String("QSQLITE") || driverName == QLatin1String("QSQLITE2")) { 66 | return NOrmDatabase::SQLite; 67 | } else if (driverName == QLatin1String("QPSQL")) { 68 | return NOrmDatabase::PostgreSQL; 69 | } else if (driverName == QLatin1String("QODBC")) { 70 | QSqlQuery query(db); 71 | if (query.exec("SELECT sqlite_version()")) 72 | return NOrmDatabase::SQLite; 73 | 74 | if (query.exec("SELECT @@version") && query.next() && query.value(0).toString().contains("Microsoft SQL Server")) 75 | return NOrmDatabase::MSSqlServer; 76 | 77 | if (query.exec("SELECT version()") && query.next()) { 78 | if (query.value(0).toString().contains("PostgreSQL")) { 79 | return NOrmDatabase::PostgreSQL; 80 | } else { 81 | return NOrmDatabase::MySqlServer; 82 | } 83 | } 84 | 85 | if (query.exec("SELECT * from v$version") && query.next()) { 86 | if (query.value(0).toString().contains("DM Database Server")) { 87 | return NOrmDatabase::DaMeng; 88 | } 89 | } else { 90 | if (globalDebugEnabled) { 91 | qWarning() << "SQL: " << query.executedQuery() << " SQL error: " << query.lastError(); 92 | } 93 | } 94 | } 95 | return NOrmDatabase::UnknownDB; 96 | } 97 | 98 | static bool initDatabase(QSqlDatabase db) 99 | { 100 | // 数据库的默认创建(目前仅支持 sqlite 和 mysql) 101 | NOrmDatabase::DatabaseType databaseType = NOrmDatabase::databaseType(db); 102 | 103 | // sqlite 104 | if (databaseType == NOrmDatabase::SQLite) { 105 | // 确保外见约束的处理 106 | NOrmQuery query(db); 107 | query.prepare("PRAGMA foreign_keys=on"); 108 | query.exec(); 109 | } else if (databaseType == NOrmDatabase::MySqlServer) { 110 | // create db if not exist 111 | auto dbName = QObject::tr("%1_tmp").arg(QString::number(quintptr(QThread::currentThreadId()))); 112 | { 113 | QSqlDatabase tmpDB = QSqlDatabase::addDatabase("QMYSQL", dbName); 114 | tmpDB.setHostName(db.hostName()); 115 | tmpDB.setPassword(db.password()); 116 | tmpDB.setPort(db.port()); 117 | tmpDB.setUserName(db.userName()); 118 | if(!tmpDB.open()){ 119 | qWarning()<<"db open error:"<thread()) 176 | return globalDatabase->reference; 177 | 178 | // 该链接已经创建过则直接返回 179 | QMutexLocker locker(&globalDatabase->mutex); 180 | if (globalDatabase->copies.contains(thread)) 181 | return globalDatabase->copies[thread]; 182 | 183 | // 新的线程则创建一个新的数据库链接 184 | QObject::connect(thread, SIGNAL(finished()), globalDatabase, SLOT(threadFinished())); 185 | QSqlDatabase db = QSqlDatabase::cloneDatabase(globalDatabase->reference, QLatin1String(connectionPrefix) + QString::number(globalDatabase->connectionId++)); 186 | if(db.open()){ 187 | initDatabase(db); 188 | if(globalDebugEnabled){ 189 | qDebug() << "线程:" << QThread::currentThread() << "创建了一个新的数据库链接 " << globalDatabase->connectionId; 190 | } 191 | } else { 192 | qWarning() << "线程:" << QThread::currentThread() << "创建了一个新的数据库链接失败 " << globalDatabase->connectionId; 193 | } 194 | globalDatabase->copies.insert(thread, db); 195 | return db; 196 | } 197 | 198 | bool NOrm::setDatabase(QSqlDatabase database) 199 | { 200 | // 数据库确保打开 201 | bool ret_openDB = database.isOpen(); 202 | if (!ret_openDB){ 203 | ret_openDB = database.open(); 204 | if (!ret_openDB) { 205 | return false; 206 | } 207 | } 208 | globalDatabaseType = getDatabaseType(database); 209 | if (globalDatabaseType == NOrmDatabase::UnknownDB) { 210 | qWarning() << "Unsupported database driver" << database.driverName(); 211 | } 212 | 213 | if (!globalDatabase) { 214 | globalDatabase = new NOrmDatabase(); 215 | qAddPostRoutine(closeDatabase); 216 | } 217 | 218 | // 初始化数据库 219 | bool ret = initDatabase(database); 220 | 221 | globalDatabase->reference = database; 222 | return ret && ret_openDB; 223 | } 224 | 225 | bool NOrm::isDebugEnabled() 226 | { 227 | return globalDebugEnabled; 228 | } 229 | 230 | void NOrm::setDebugEnabled(bool enabled) 231 | { 232 | globalDebugEnabled = enabled; 233 | } 234 | 235 | static void norm_topsort(const QByteArray &modelName, 236 | QHash &visited, 237 | QStack &stack) 238 | { 239 | visited[modelName] = true; 240 | NOrmMetaModel model = globalMetaModels[modelName]; 241 | foreach (const QByteArray &foreignModel, model.foreignFields().values()) { 242 | if (!visited[foreignModel]) 243 | norm_topsort(foreignModel, visited, stack); 244 | } 245 | 246 | stack.push(model); 247 | } 248 | 249 | static QStack norm_sorted_metamodels() 250 | { 251 | QStack stack; 252 | stack.reserve(globalMetaModels.size()); 253 | QHash visited; 254 | visited.reserve(globalMetaModels.size()); 255 | foreach (const QByteArray &model, globalMetaModels.keys()) 256 | visited[model] = false; 257 | 258 | foreach (const QByteArray &model, globalMetaModels.keys()) { 259 | if (!visited[model]) 260 | norm_topsort(model, visited, stack); 261 | } 262 | 263 | return stack; 264 | } 265 | 266 | bool NOrm::createTables() 267 | { 268 | bool result = true; 269 | QStack stack = norm_sorted_metamodels(); 270 | foreach (const NOrmMetaModel &model, stack) { 271 | if (!model.createTable()) 272 | result = false; 273 | } 274 | 275 | return result; 276 | } 277 | 278 | bool NOrm::dropTables() 279 | { 280 | bool result = true; 281 | QStack stack = norm_sorted_metamodels(); 282 | for (int i = stack.size() - 1; i >= 0; --i) { 283 | NOrmMetaModel model = stack.at(i); 284 | if (!model.dropTable()) 285 | result = false; 286 | } 287 | 288 | return result; 289 | } 290 | 291 | NOrmMetaModel NOrm::metaModel(const char *name) 292 | { 293 | if (globalMetaModels.contains(name)) 294 | return globalMetaModels.value(name); 295 | 296 | // 直接去全局做名字匹配 297 | foreach (QByteArray modelName, globalMetaModels.keys()) { 298 | if (qstricmp(name, modelName.data()) == 0) 299 | return globalMetaModels.value(modelName); 300 | } 301 | 302 | return NOrmMetaModel(); 303 | } 304 | 305 | QStack NOrm::metaModels() 306 | { 307 | return norm_sorted_metamodels(); 308 | } 309 | 310 | NOrmMetaModel NOrm::registerModel(const QMetaObject *meta) 311 | { 312 | const QByteArray name = meta->className(); 313 | if (!globalMetaModels.contains(name)) 314 | globalMetaModels.insert(name, NOrmMetaModel(meta)); 315 | return globalMetaModels[name]; 316 | } 317 | 318 | NOrmDatabase::DatabaseType NOrmDatabase::databaseType(const QSqlDatabase &db) 319 | { 320 | Q_UNUSED(db); 321 | return globalDatabaseType; 322 | } 323 | -------------------------------------------------------------------------------- /NORM/inc/NOrmQuerySet.h: -------------------------------------------------------------------------------- 1 | #ifndef NORM_QUERYSET_H 2 | #define NORM_QUERYSET_H 3 | 4 | /* 5 | * 描述: NORM 结果集合 6 | * 作者: daodaoliang@yeah.net 7 | * 时间: 2021-07-16 8 | */ 9 | 10 | #include "NOrm.h" 11 | #include "NOrmWhere.h" 12 | #include "NOrmQuerySet_p.h" 13 | 14 | template class NOrmQuerySet { 15 | public: 16 | typedef int size_type; 17 | typedef T value_type; 18 | typedef value_type *pointer; 19 | typedef const value_type *const_pointer; 20 | typedef value_type &reference; 21 | typedef const value_type &const_reference; 22 | typedef qptrdiff difference_type; 23 | 24 | class const_iterator { 25 | friend class NOrmQuerySet; 26 | 27 | public: 28 | typedef std::bidirectional_iterator_tag iterator_category; 29 | typedef qptrdiff difference_type; 30 | typedef T value_type; 31 | typedef T *pointer; 32 | typedef T &reference; 33 | 34 | const_iterator() : m_querySet(0), m_fetched(-1), m_offset(0) {} 35 | const_iterator(const const_iterator &other) : m_querySet(other.m_querySet), m_fetched(-1), m_offset(other.m_offset) {} 36 | 37 | private: 38 | const_iterator(const NOrmQuerySet *querySet, int offset = 0) : m_querySet(querySet), m_fetched(-1), m_offset(offset) {} 39 | 40 | public: 41 | const T &operator*() const { 42 | return *t(); 43 | } 44 | 45 | const T *operator->() const { 46 | return t(); 47 | } 48 | 49 | bool operator==(const const_iterator &other) const { 50 | return m_querySet == other.m_querySet && m_offset == other.m_offset; 51 | } 52 | 53 | bool operator!=(const const_iterator &other) const { 54 | return m_querySet != other.m_querySet || m_offset != other.m_offset; 55 | } 56 | 57 | bool operator<(const const_iterator &other) const { 58 | return (m_querySet == other.m_querySet && m_offset < other.m_offset) || m_querySet < other.m_querySet; 59 | } 60 | 61 | bool operator<=(const const_iterator &other) const { 62 | return (m_querySet == other.m_querySet && m_offset <= other.m_offset) || m_querySet < other.m_querySet; 63 | } 64 | 65 | bool operator>(const const_iterator &other) const { 66 | return (m_querySet == other.m_querySet && m_offset > other.m_offset) || m_querySet > other.m_querySet; 67 | } 68 | 69 | bool operator>=(const const_iterator &other) const { 70 | return (m_querySet == other.m_querySet && m_offset >= other.m_offset) || m_querySet > other.m_querySet; 71 | } 72 | 73 | const_iterator &operator++() { 74 | ++m_offset; 75 | return *this; 76 | } 77 | const_iterator operator++(int) { 78 | const_iterator n(*this); 79 | ++m_offset; 80 | return n; 81 | } 82 | const_iterator &operator+=(int i) { 83 | m_offset += i; 84 | return *this; 85 | } 86 | const_iterator operator+(int i) const { return const_iterator(m_querySet, m_offset + i); } 87 | const_iterator &operator-=(int i) { 88 | m_offset -= i; 89 | return *this; 90 | } 91 | const_iterator operator-(int i) const { return const_iterator(m_querySet, m_offset - i); } 92 | const_iterator &operator--() { 93 | --m_offset; 94 | return *this; 95 | } 96 | const_iterator operator--(int) { 97 | const_iterator n(*this); 98 | --m_offset; 99 | return n; 100 | } 101 | difference_type operator-(const const_iterator &other) const { return m_offset - other.m_offset; } 102 | 103 | private: 104 | const T *t() const { 105 | if (m_fetched != m_offset && m_querySet) { 106 | if (const_cast *>(m_querySet)->at(m_offset, &m_object)) { 107 | m_fetched = m_offset; 108 | } 109 | } 110 | 111 | return m_fetched == m_offset ? &m_object : 0; 112 | } 113 | 114 | private: 115 | const NOrmQuerySet *m_querySet; 116 | mutable int m_fetched; 117 | mutable T m_object; 118 | int m_offset; 119 | }; 120 | 121 | typedef const_iterator ConstIterator; 122 | 123 | NOrmQuerySet(); 124 | NOrmQuerySet(const NOrmQuerySet &other); 125 | ~NOrmQuerySet(); 126 | 127 | NOrmQuerySet all() const; 128 | NOrmQuerySet exclude(const NOrmWhere &where) const; 129 | NOrmQuerySet filter(const NOrmWhere &where) const; 130 | NOrmQuerySet limit(int pos, int length = -1) const; 131 | NOrmQuerySet none() const; 132 | NOrmQuerySet orderBy(const QStringList &keys) const; 133 | NOrmQuerySet selectRelated(const QStringList &relatedFields = QStringList()) const; 134 | 135 | int count() const; 136 | QVariant aggregate(const NOrmWhere::AggregateType func, const QString &field) const; 137 | NOrmWhere where() const; 138 | 139 | bool remove(); 140 | int size(); 141 | int update(const QVariantMap &fields); 142 | QList values(const QStringList &fields = QStringList()); 143 | QList valuesList(const QStringList &fields = QStringList()); 144 | 145 | T *get(const NOrmWhere &where, T *target = 0) const; 146 | T *at(int index, T *target = 0); 147 | 148 | const_iterator constBegin() const; 149 | const_iterator begin() const; 150 | 151 | const_iterator constEnd() const; 152 | const_iterator end() const; 153 | 154 | NOrmQuerySet &operator=(const NOrmQuerySet &other); 155 | 156 | private: 157 | NOrmQuerySetPrivate *d; 158 | }; 159 | 160 | template NOrmQuerySet::NOrmQuerySet() { 161 | d = new NOrmQuerySetPrivate(T::staticMetaObject.className()); 162 | } 163 | 164 | template NOrmQuerySet::NOrmQuerySet(const NOrmQuerySet &other) { 165 | other.d->counter.ref(); 166 | d = other.d; 167 | } 168 | 169 | template NOrmQuerySet::~NOrmQuerySet() { 170 | if (!d->counter.deref()) 171 | delete d; 172 | } 173 | 174 | template T *NOrmQuerySet::at(int index, T *target) { 175 | T *entry = target ? target : new T; 176 | if (!d->sqlLoad(entry, index)) { 177 | if (!target) 178 | delete entry; 179 | return 0; 180 | } 181 | return entry; 182 | } 183 | 184 | template typename NOrmQuerySet::const_iterator NOrmQuerySet::constBegin() const { 185 | return const_iterator(this); 186 | } 187 | 188 | template typename NOrmQuerySet::const_iterator NOrmQuerySet::begin() const { 189 | return const_iterator(this); 190 | } 191 | 192 | template typename NOrmQuerySet::const_iterator NOrmQuerySet::constEnd() const { 193 | return const_iterator(this, NOrmQuerySet::count()); 194 | } 195 | 196 | template typename NOrmQuerySet::const_iterator NOrmQuerySet::end() const { 197 | return const_iterator(this, NOrmQuerySet::count()); 198 | } 199 | 200 | template NOrmQuerySet NOrmQuerySet::all() const { 201 | NOrmQuerySet other; 202 | other.d->lowMark = d->lowMark; 203 | other.d->highMark = d->highMark; 204 | other.d->orderBy = d->orderBy; 205 | other.d->selectRelated = d->selectRelated; 206 | other.d->relatedFields = d->relatedFields; 207 | other.d->whereClause = d->whereClause; 208 | return other; 209 | } 210 | 211 | template int NOrmQuerySet::count() const { 212 | if (d->hasResults) 213 | return d->properties.size(); 214 | 215 | QVariant count(aggregate(NOrmWhere::COUNT, "*")); 216 | return count.isValid() ? count.toInt() : -1; 217 | } 218 | 219 | template 220 | QVariant NOrmQuerySet::aggregate(const NOrmWhere::AggregateType func, const QString &field) const { 221 | // execute aggregate query 222 | NOrmQuery query(d->aggregateQuery(func, field)); 223 | if (!query.exec() || !query.next()) 224 | return QVariant(); 225 | return query.value(0); 226 | } 227 | 228 | template NOrmQuerySet NOrmQuerySet::exclude(const NOrmWhere &where) const { 229 | NOrmQuerySet other = all(); 230 | other.d->addFilter(!where); 231 | return other; 232 | } 233 | 234 | template NOrmQuerySet NOrmQuerySet::filter(const NOrmWhere &where) const { 235 | NOrmQuerySet other = all(); 236 | other.d->addFilter(where); 237 | return other; 238 | } 239 | 240 | template T *NOrmQuerySet::get(const NOrmWhere &where, T *target) const { 241 | NOrmQuerySet qs = filter(where); 242 | return qs.size() == 1 ? qs.at(0, target) : 0; 243 | } 244 | 245 | template NOrmQuerySet NOrmQuerySet::limit(int pos, int length) const { 246 | Q_ASSERT(pos >= 0); 247 | Q_ASSERT(length >= -1); 248 | 249 | NOrmQuerySet other = all(); 250 | other.d->lowMark += pos; 251 | if (length > 0) { 252 | other.d->highMark = other.d->lowMark + length; 253 | // never exceed the current high mark 254 | if (d->highMark > 0 && other.d->highMark > d->highMark) 255 | other.d->highMark = d->highMark; 256 | } 257 | return other; 258 | } 259 | 260 | template NOrmQuerySet NOrmQuerySet::none() const { 261 | NOrmQuerySet other; 262 | other.d->whereClause = !NOrmWhere(); 263 | return other; 264 | } 265 | 266 | template NOrmQuerySet NOrmQuerySet::orderBy(const QStringList &keys) const { 267 | Q_ASSERT(!d->lowMark && !d->highMark); 268 | NOrmQuerySet other = all(); 269 | other.d->orderBy << keys; 270 | return other; 271 | } 272 | 273 | template bool NOrmQuerySet::remove() { 274 | return d->sqlDelete(); 275 | } 276 | 277 | template NOrmQuerySet NOrmQuerySet::selectRelated(const QStringList &relatedFields) const { 278 | NOrmQuerySet other = all(); 279 | other.d->selectRelated = true; 280 | other.d->relatedFields = relatedFields; 281 | return other; 282 | } 283 | 284 | template int NOrmQuerySet::size() { 285 | if (!d->sqlFetch()) 286 | return -1; 287 | return d->properties.size(); 288 | } 289 | 290 | template int NOrmQuerySet::update(const QVariantMap &fields) { 291 | return d->sqlUpdate(fields); 292 | } 293 | 294 | template QList NOrmQuerySet::values(const QStringList &fields) { 295 | return d->sqlValues(fields); 296 | } 297 | 298 | template QList NOrmQuerySet::valuesList(const QStringList &fields) { 299 | return d->sqlValuesList(fields); 300 | } 301 | 302 | template NOrmWhere NOrmQuerySet::where() const { 303 | return d->resolvedWhere(NOrm::database()); 304 | } 305 | 306 | template NOrmQuerySet &NOrmQuerySet::operator=(const NOrmQuerySet &other) { 307 | other.d->counter.ref(); 308 | if (!d->counter.deref()) 309 | delete d; 310 | d = other.d; 311 | return *this; 312 | } 313 | 314 | #endif 315 | -------------------------------------------------------------------------------- /docs/_res/search.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var INDEXS = {}; 3 | var helper; 4 | 5 | function escapeHtml(string) { 6 | var entityMap = { 7 | '&': '&', 8 | '<': '<', 9 | '>': '>', 10 | '"': '"', 11 | '\'': ''', 12 | '/': '/' 13 | }; 14 | 15 | return String(string).replace(/[&<>"'/]/g, function (s) { return entityMap[s]; }) 16 | } 17 | 18 | function getAllPaths(router) { 19 | var paths = []; 20 | 21 | helper.dom.findAll('a:not([data-nosearch])').forEach(function (node) { 22 | var href = node.href; 23 | var originHref = node.getAttribute('href'); 24 | var path = router.parse(href).path; 25 | 26 | if ( 27 | path && 28 | paths.indexOf(path) === -1 && 29 | !Docsify.util.isAbsolutePath(originHref) 30 | ) { 31 | paths.push(path); 32 | } 33 | }); 34 | 35 | return paths 36 | } 37 | 38 | function saveData(maxAge) { 39 | localStorage.setItem('docsify.search.expires', Date.now() + maxAge); 40 | localStorage.setItem('docsify.search.index', JSON.stringify(INDEXS)); 41 | } 42 | 43 | function genIndex(path, content, router, depth) { 44 | if ( content === void 0 ) content = ''; 45 | 46 | var tokens = window.marked.lexer(content); 47 | var slugify = window.Docsify.slugify; 48 | var index = {}; 49 | var slug; 50 | 51 | tokens.forEach(function (token) { 52 | if (token.type === 'heading' && token.depth <= depth) { 53 | slug = router.toURL(path, {id: slugify(token.text)}); 54 | index[slug] = {slug: slug, title: token.text, body: ''}; 55 | } else { 56 | if (!slug) { 57 | return 58 | } 59 | if (!index[slug]) { 60 | index[slug] = {slug: slug, title: '', body: ''}; 61 | } else if (index[slug].body) { 62 | index[slug].body += '\n' + (token.text || ''); 63 | } else { 64 | index[slug].body = token.text; 65 | } 66 | } 67 | }); 68 | slugify.clear(); 69 | return index 70 | } 71 | 72 | /** 73 | * @param {String} query 74 | * @returns {Array} 75 | */ 76 | function search(query) { 77 | var matchingResults = []; 78 | var data = []; 79 | Object.keys(INDEXS).forEach(function (key) { 80 | data = data.concat(Object.keys(INDEXS[key]).map(function (page) { return INDEXS[key][page]; })); 81 | }); 82 | 83 | query = query.trim(); 84 | var keywords = query.split(/[\s\-,\\/]+/); 85 | if (keywords.length !== 1) { 86 | keywords = [].concat(query, keywords); 87 | } 88 | 89 | var loop = function ( i ) { 90 | var post = data[i]; 91 | var isMatch = false; 92 | var resultStr = ''; 93 | var postTitle = post.title && post.title.trim(); 94 | var postContent = post.body && post.body.trim(); 95 | var postUrl = post.slug || ''; 96 | 97 | if (postTitle && postContent) { 98 | keywords.forEach(function (keyword) { 99 | // From https://github.com/sindresorhus/escape-string-regexp 100 | var regEx = new RegExp( 101 | keyword.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'), 102 | 'gi' 103 | ); 104 | var indexTitle = -1; 105 | var indexContent = -1; 106 | 107 | indexTitle = postTitle && postTitle.search(regEx); 108 | indexContent = postContent && postContent.search(regEx); 109 | 110 | if (indexTitle < 0 && indexContent < 0) { 111 | isMatch = false; 112 | } else { 113 | isMatch = true; 114 | if (indexContent < 0) { 115 | indexContent = 0; 116 | } 117 | 118 | var start = 0; 119 | var end = 0; 120 | 121 | start = indexContent < 11 ? 0 : indexContent - 10; 122 | end = start === 0 ? 70 : indexContent + keyword.length + 60; 123 | 124 | if (end > postContent.length) { 125 | end = postContent.length; 126 | } 127 | 128 | var matchContent = 129 | '...' + 130 | escapeHtml(postContent) 131 | .substring(start, end) 132 | .replace(regEx, ("" + keyword + "")) + 133 | '...'; 134 | 135 | resultStr += matchContent; 136 | } 137 | }); 138 | 139 | if (isMatch) { 140 | var matchingPost = { 141 | title: escapeHtml(postTitle), 142 | content: resultStr, 143 | url: postUrl 144 | }; 145 | 146 | matchingResults.push(matchingPost); 147 | } 148 | } 149 | }; 150 | 151 | for (var i = 0; i < data.length; i++) loop( i ); 152 | 153 | return matchingResults 154 | } 155 | 156 | function init$1(config, vm) { 157 | helper = Docsify; 158 | 159 | var isAuto = config.paths === 'auto'; 160 | var isExpired = localStorage.getItem('docsify.search.expires') < Date.now(); 161 | 162 | INDEXS = JSON.parse(localStorage.getItem('docsify.search.index')); 163 | 164 | if (isExpired) { 165 | INDEXS = {}; 166 | } else if (!isAuto) { 167 | return 168 | } 169 | 170 | var paths = isAuto ? getAllPaths(vm.router) : config.paths; 171 | var len = paths.length; 172 | var count = 0; 173 | 174 | paths.forEach(function (path) { 175 | if (INDEXS[path]) { 176 | return count++ 177 | } 178 | 179 | helper 180 | .get(vm.router.getFile(path), false, vm.config.requestHeaders) 181 | .then(function (result) { 182 | INDEXS[path] = genIndex(path, result, vm.router, config.depth); 183 | len === ++count && saveData(config.maxAge); 184 | }); 185 | }); 186 | } 187 | 188 | var NO_DATA_TEXT = ''; 189 | 190 | function style() { 191 | var code = "\n.sidebar {\n padding-top: 0;\n}\n\n.search {\n margin-bottom: 20px;\n padding: 6px;\n border-bottom: 1px solid #eee;\n}\n\n.search .input-wrap {\n display: flex;\n align-items: center;\n}\n\n.search .results-panel {\n display: none;\n}\n\n.search .results-panel.show {\n display: block;\n}\n\n.search input {\n outline: none;\n border: none;\n width: 100%;\n padding: 0 7px;\n line-height: 36px;\n font-size: 14px;\n}\n\n.search input::-webkit-search-decoration,\n.search input::-webkit-search-cancel-button,\n.search input {\n -webkit-appearance: none;\n -moz-appearance: none;\n appearance: none;\n}\n.search .clear-button {\n width: 36px;\n text-align: right;\n display: none;\n}\n\n.search .clear-button.show {\n display: block;\n}\n\n.search .clear-button svg {\n transform: scale(.5);\n}\n\n.search h2 {\n font-size: 17px;\n margin: 10px 0;\n}\n\n.search a {\n text-decoration: none;\n color: inherit;\n}\n\n.search .matching-post {\n border-bottom: 1px solid #eee;\n}\n\n.search .matching-post:last-child {\n border-bottom: 0;\n}\n\n.search p {\n font-size: 14px;\n overflow: hidden;\n text-overflow: ellipsis;\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n}\n\n.search p.empty {\n text-align: center;\n}"; 192 | 193 | Docsify.dom.style(code); 194 | } 195 | 196 | function tpl(opts, defaultValue) { 197 | if ( defaultValue === void 0 ) defaultValue = ''; 198 | 199 | var html = 200 | "
\n \n
\n \n \n \n \n \n
\n
\n
\n "; 201 | var el = Docsify.dom.create('div', html); 202 | var aside = Docsify.dom.find('aside'); 203 | 204 | Docsify.dom.toggleClass(el, 'search'); 205 | Docsify.dom.before(aside, el); 206 | } 207 | 208 | function doSearch(value) { 209 | var $search = Docsify.dom.find('div.search'); 210 | var $panel = Docsify.dom.find($search, '.results-panel'); 211 | var $clearBtn = Docsify.dom.find($search, '.clear-button'); 212 | 213 | if (!value) { 214 | $panel.classList.remove('show'); 215 | $clearBtn.classList.remove('show'); 216 | $panel.innerHTML = ''; 217 | return 218 | } 219 | var matchs = search(value); 220 | 221 | var html = ''; 222 | matchs.forEach(function (post) { 223 | html += ""; 224 | }); 225 | 226 | $panel.classList.add('show'); 227 | $clearBtn.classList.add('show'); 228 | $panel.innerHTML = html || ("

" + NO_DATA_TEXT + "

"); 229 | } 230 | 231 | function bindEvents() { 232 | var $search = Docsify.dom.find('div.search'); 233 | var $input = Docsify.dom.find($search, 'input'); 234 | var $inputWrap = Docsify.dom.find($search, '.input-wrap'); 235 | 236 | var timeId; 237 | // Prevent to Fold sidebar 238 | Docsify.dom.on( 239 | $search, 240 | 'click', 241 | function (e) { return e.target.tagName !== 'A' && e.stopPropagation(); } 242 | ); 243 | Docsify.dom.on($input, 'input', function (e) { 244 | clearTimeout(timeId); 245 | timeId = setTimeout(function (_) { return doSearch(e.target.value.trim()); }, 100); 246 | }); 247 | Docsify.dom.on($inputWrap, 'click', function (e) { 248 | // Click input outside 249 | if (e.target.tagName !== 'INPUT') { 250 | $input.value = ''; 251 | doSearch(); 252 | } 253 | }); 254 | } 255 | 256 | function updatePlaceholder(text, path) { 257 | var $input = Docsify.dom.getNode('.search input[type="search"]'); 258 | 259 | if (!$input) { 260 | return 261 | } 262 | if (typeof text === 'string') { 263 | $input.placeholder = text; 264 | } else { 265 | var match = Object.keys(text).filter(function (key) { return path.indexOf(key) > -1; })[0]; 266 | $input.placeholder = text[match]; 267 | } 268 | } 269 | 270 | function updateNoData(text, path) { 271 | if (typeof text === 'string') { 272 | NO_DATA_TEXT = text; 273 | } else { 274 | var match = Object.keys(text).filter(function (key) { return path.indexOf(key) > -1; })[0]; 275 | NO_DATA_TEXT = text[match]; 276 | } 277 | } 278 | 279 | function init(opts, vm) { 280 | var keywords = vm.router.parse().query.s; 281 | 282 | style(); 283 | tpl(opts, keywords); 284 | bindEvents(); 285 | keywords && setTimeout(function (_) { return doSearch(keywords); }, 500); 286 | } 287 | 288 | function update(opts, vm) { 289 | updatePlaceholder(opts.placeholder, vm.route.path); 290 | updateNoData(opts.noData, vm.route.path); 291 | } 292 | 293 | var CONFIG = { 294 | placeholder: 'Type to search', 295 | noData: 'No Results!', 296 | paths: 'auto', 297 | depth: 2, 298 | maxAge: 86400000 // 1 day 299 | }; 300 | 301 | var install = function (hook, vm) { 302 | var util = Docsify.util; 303 | var opts = vm.config.search || CONFIG; 304 | 305 | if (Array.isArray(opts)) { 306 | CONFIG.paths = opts; 307 | } else if (typeof opts === 'object') { 308 | CONFIG.paths = Array.isArray(opts.paths) ? opts.paths : 'auto'; 309 | CONFIG.maxAge = util.isPrimitive(opts.maxAge) ? opts.maxAge : CONFIG.maxAge; 310 | CONFIG.placeholder = opts.placeholder || CONFIG.placeholder; 311 | CONFIG.noData = opts.noData || CONFIG.noData; 312 | CONFIG.depth = opts.depth || CONFIG.depth; 313 | } 314 | 315 | var isAuto = CONFIG.paths === 'auto'; 316 | 317 | hook.mounted(function (_) { 318 | init(CONFIG, vm); 319 | !isAuto && init$1(CONFIG, vm); 320 | }); 321 | hook.doneEach(function (_) { 322 | update(CONFIG, vm); 323 | isAuto && init$1(CONFIG, vm); 324 | }); 325 | }; 326 | 327 | $docsify.plugins = [].concat(install, $docsify.plugins); 328 | 329 | }()); -------------------------------------------------------------------------------- /docs/_res/vue.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css?family=Roboto+Mono|Source+Sans+Pro:300,400,600");*{-webkit-font-smoothing:antialiased;-webkit-overflow-scrolling:touch;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-text-size-adjust:none;-webkit-touch-callout:none;box-sizing:border-box}body:not(.ready){overflow:hidden}body:not(.ready) .app-nav,body:not(.ready)>nav,body:not(.ready) [data-cloak]{display:none}div#app{font-size:30px;font-weight:lighter;margin:40vh auto;text-align:center}div#app:empty:before{content:"Loading..."}.emoji{height:1.2rem;vertical-align:middle}.progress{background-color:var(--theme-color,#42b983);height:2px;left:0;position:fixed;right:0;top:0;transition:width .2s,opacity .4s;width:0;z-index:5}.search .search-keyword,.search a:hover{color:var(--theme-color,#42b983)}.search .search-keyword{font-style:normal;font-weight:700}body,html{height:100%}body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;color:#34495e;font-family:Source Sans Pro,Helvetica Neue,Arial,sans-serif;font-size:15px;letter-spacing:0;margin:0;overflow-x:hidden}img{max-width:100%}a[disabled]{cursor:not-allowed;opacity:.6}kbd{border:1px solid #ccc;border-radius:3px;display:inline-block;font-size:12px!important;line-height:12px;margin-bottom:3px;padding:3px 5px;vertical-align:middle}.task-list-item{list-style-type:none}li input[type=checkbox]{margin:0 .2em .25em -1.6em;vertical-align:middle}.app-nav{margin:25px 60px 0 0;position:absolute;right:0;text-align:right;z-index:2}.app-nav.no-badge{margin-right:25px}.app-nav p{margin:0}.app-nav>a{margin:0 1rem;padding:5px 0}.app-nav li,.app-nav ul{display:inline-block;list-style:none;margin:0}.app-nav a{color:inherit;font-size:16px;text-decoration:none;transition:color .3s}.app-nav a.active,.app-nav a:hover{color:var(--theme-color,#42b983)}.app-nav a.active{border-bottom:2px solid var(--theme-color,#42b983)}.app-nav li{display:inline-block;margin:0 1rem;padding:5px 0;position:relative}.app-nav li ul{background-color:#fff;border:1px solid #ddd;border-bottom-color:#ccc;border-radius:4px;box-sizing:border-box;display:none;max-height:calc(100vh - 61px);overflow-y:auto;padding:10px 0;position:absolute;right:-15px;text-align:left;top:100%;white-space:nowrap}.app-nav li ul li{display:block;font-size:14px;line-height:1rem;margin:0;margin:8px 14px;white-space:nowrap}.app-nav li ul a{display:block;font-size:inherit;margin:0;padding:0}.app-nav li ul a.active{border-bottom:0}.app-nav li:hover ul{display:block}.github-corner{border-bottom:0;position:fixed;right:0;text-decoration:none;top:0;z-index:1}.github-corner:hover .octo-arm{animation:a .56s ease-in-out}.github-corner svg{color:#fff;fill:var(--theme-color,#42b983);height:80px;width:80px}main{display:block;position:relative;width:100vw;height:100%;z-index:0}main.hidden{display:none}.anchor{display:inline-block;text-decoration:none;transition:all .3s}.anchor span{color:#34495e}.anchor:hover{text-decoration:underline}.sidebar{border-right:1px solid rgba(0,0,0,.07);overflow-y:auto;padding:40px 0 0;position:absolute;top:0;bottom:0;left:0;transition:transform .25s ease-out;width:300px;z-index:3}.sidebar>h1{margin:0 auto 1rem;font-size:1.5rem;font-weight:300;text-align:center}.sidebar>h1 a{color:inherit;text-decoration:none}.sidebar>h1 .app-nav{display:block;position:static}.sidebar .sidebar-nav{line-height:2em;padding-bottom:40px}.sidebar li.collapse .app-sub-sidebar{display:none}.sidebar ul{margin:0;padding:0}.sidebar li>p{font-weight:700;margin:0}.sidebar ul,.sidebar ul li{list-style:none}.sidebar ul li a{border-bottom:none;display:block}.sidebar ul li ul{padding-left:20px}.sidebar::-webkit-scrollbar{width:4px}.sidebar::-webkit-scrollbar-thumb{background:transparent;border-radius:4px}.sidebar:hover::-webkit-scrollbar-thumb{background:hsla(0,0%,53%,.4)}.sidebar:hover::-webkit-scrollbar-track{background:hsla(0,0%,53%,.1)}.sidebar-toggle{background-color:transparent;background-color:hsla(0,0%,100%,.8);border:0;outline:none;padding:10px;position:absolute;bottom:0;left:0;text-align:center;transition:opacity .3s;width:284px;z-index:4}.sidebar-toggle .sidebar-toggle-button:hover{opacity:.4}.sidebar-toggle span{background-color:var(--theme-color,#42b983);display:block;margin-bottom:4px;width:16px;height:2px}body.sticky .sidebar,body.sticky .sidebar-toggle{position:fixed}.content{padding-top:60px;position:absolute;top:0;right:0;bottom:0;left:300px;transition:left .25s ease}.markdown-section{margin:0 auto;max-width:800px;padding:30px 15px 40px;position:relative}.markdown-section>*{box-sizing:border-box;font-size:inherit}.markdown-section>:first-child{margin-top:0!important}.markdown-section hr{border:none;border-bottom:1px solid #eee;margin:2em 0}.markdown-section iframe{border:1px solid #eee}.markdown-section table{border-collapse:collapse;border-spacing:0;display:block;margin-bottom:1rem;overflow:auto;width:100%}.markdown-section th{font-weight:700}.markdown-section td,.markdown-section th{border:1px solid #ddd;padding:6px 13px}.markdown-section tr{border-top:1px solid #ccc}.markdown-section p.tip,.markdown-section tr:nth-child(2n){background-color:#f8f8f8}.markdown-section p.tip{border-bottom-right-radius:2px;border-left:4px solid #f66;border-top-right-radius:2px;margin:2em 0;padding:12px 24px 12px 30px;position:relative}.markdown-section p.tip:before{background-color:#f66;border-radius:100%;color:#fff;content:"!";font-family:Dosis,Source Sans Pro,Helvetica Neue,Arial,sans-serif;font-size:14px;font-weight:700;left:-12px;line-height:20px;position:absolute;height:20px;width:20px;text-align:center;top:14px}.markdown-section p.tip code{background-color:#efefef}.markdown-section p.tip em{color:#34495e}.markdown-section p.warn{background:rgba(66,185,131,.1);border-radius:2px;padding:1rem}body.close .sidebar{transform:translateX(-300px)}body.close .sidebar-toggle{width:auto}body.close .content{left:0}@media print{.app-nav,.github-corner,.sidebar,.sidebar-toggle{display:none}}@media screen and (max-width:768px){.github-corner,.sidebar,.sidebar-toggle{position:fixed}.app-nav{margin-top:16px}.app-nav li ul{top:30px}main{height:auto;overflow-x:hidden}.sidebar{left:-300px;transition:transform .25s ease-out}.content{left:0;max-width:100vw;position:static;padding-top:20px;transition:transform .25s ease}.app-nav,.github-corner{transition:transform .25s ease-out}.sidebar-toggle{background-color:transparent;width:auto;padding:30px 30px 10px 10px}body.close .sidebar{transform:translateX(300px)}body.close .sidebar-toggle{background-color:hsla(0,0%,100%,.8);transition:background-color 1s;width:284px;padding:10px}body.close .content{transform:translateX(300px)}body.close .app-nav,body.close .github-corner{display:none}.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:a .56s ease-in-out}}@keyframes a{0%,to{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}section.cover{-ms-flex-align:center;align-items:center;background-position:50%;background-repeat:no-repeat;background-size:cover;height:100vh;display:none}section.cover.show{display:-ms-flexbox;display:flex}section.cover.has-mask .mask{background-color:#fff;opacity:.8;position:absolute;top:0;height:100%;width:100%}section.cover .cover-main{-ms-flex:1;flex:1;margin:-20px 16px 0;text-align:center;z-index:1}section.cover a{color:inherit}section.cover a,section.cover a:hover{text-decoration:none}section.cover p{line-height:1.5rem;margin:1em 0}section.cover h1{color:inherit;font-size:2.5rem;font-weight:300;margin:.625rem 0 2.5rem;position:relative;text-align:center}section.cover h1 a{display:block}section.cover h1 small{bottom:-.4375rem;font-size:1rem;position:absolute}section.cover blockquote{font-size:1.5rem;text-align:center}section.cover ul{line-height:1.8;list-style-type:none;margin:1em auto;max-width:500px;padding:0}section.cover .cover-main>p:last-child a{border:1px solid var(--theme-color,#42b983);border-radius:2rem;box-sizing:border-box;color:var(--theme-color,#42b983);display:inline-block;font-size:1.05rem;letter-spacing:.1rem;margin:.5rem 1rem;padding:.75em 2rem;text-decoration:none;transition:all .15s ease}section.cover .cover-main>p:last-child a:last-child{background-color:var(--theme-color,#42b983);color:#fff}section.cover .cover-main>p:last-child a:last-child:hover{color:inherit;opacity:.8}section.cover .cover-main>p:last-child a:hover{color:inherit}section.cover blockquote>p>a{border-bottom:2px solid var(--theme-color,#42b983);transition:color .3s}section.cover blockquote>p>a:hover{color:var(--theme-color,#42b983)}.sidebar,body{background-color:#fff}.sidebar{color:#364149}.sidebar li{margin:6px 0 6px 15px}.sidebar ul li a{color:#505d6b;font-size:14px;font-weight:400;overflow:hidden;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.sidebar ul li a:hover{text-decoration:underline}.sidebar ul li ul{padding:0}.sidebar ul li.active>a{border-right:2px solid;color:var(--theme-color,#42b983);font-weight:600}.app-sub-sidebar li:before{content:"-";padding-right:4px;float:left}.markdown-section h1,.markdown-section h2,.markdown-section h3,.markdown-section h4,.markdown-section strong{color:#2c3e50;font-weight:600}.markdown-section a{color:var(--theme-color,#42b983);font-weight:600}.markdown-section h1{font-size:2rem;margin:0 0 1rem}.markdown-section h2{font-size:1.75rem;margin:45px 0 .8rem}.markdown-section h3{font-size:1.5rem;margin:40px 0 .6rem}.markdown-section h4{font-size:1.25rem}.markdown-section h5{font-size:1rem}.markdown-section h6{color:#777;font-size:1rem}.markdown-section figure,.markdown-section p{margin:1.2em 0}.markdown-section ol,.markdown-section p,.markdown-section ul{line-height:1.6rem;word-spacing:.05rem}.markdown-section ol,.markdown-section ul{padding-left:1.5rem}.markdown-section blockquote{border-left:4px solid var(--theme-color,#42b983);color:#858585;margin:2em 0;padding-left:20px}.markdown-section blockquote p{font-weight:600;margin-left:0}.markdown-section iframe{margin:1em 0}.markdown-section em{color:#7f8c8d}.markdown-section code{border-radius:2px;color:#e96900;font-size:.8rem;margin:0 2px;padding:3px 5px;white-space:pre-wrap}.markdown-section code,.markdown-section pre{background-color:#f8f8f8;font-family:Roboto Mono,Monaco,courier,monospace}.markdown-section pre{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;line-height:1.5rem;margin:1.2em 0;overflow:auto;padding:0 1.4rem;position:relative;word-wrap:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#8e908c}.token.namespace{opacity:.7}.token.boolean,.token.number{color:#c76b29}.token.punctuation{color:#525252}.token.property{color:#c08b30}.token.tag{color:#2973b7}.token.string{color:var(--theme-color,#42b983)}.token.selector{color:#6679cc}.token.attr-name{color:#2973b7}.language-css .token.string,.style .token.string,.token.entity,.token.url{color:#22a2c9}.token.attr-value,.token.control,.token.directive,.token.unit{color:var(--theme-color,#42b983)}.token.keyword{color:#e96900}.token.atrule,.token.regex,.token.statement{color:#22a2c9}.token.placeholder,.token.variable{color:#3d8fd1}.token.deleted{text-decoration:line-through}.token.inserted{border-bottom:1px dotted #202746;text-decoration:none}.token.italic{font-style:italic}.token.bold,.token.important{font-weight:700}.token.important{color:#c94922}.token.entity{cursor:help}.markdown-section pre>code{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;background-color:#f8f8f8;border-radius:2px;color:#525252;display:block;font-family:Roboto Mono,Monaco,courier,monospace;font-size:.8rem;line-height:inherit;margin:0 2px;max-width:inherit;overflow:inherit;padding:2.2em 5px;white-space:inherit}.markdown-section code:after,.markdown-section code:before{letter-spacing:.05rem}code .token{-moz-osx-font-smoothing:initial;-webkit-font-smoothing:initial;min-height:1.5rem}pre:after{color:#ccc;content:attr(data-lang);font-size:.6rem;font-weight:600;height:15px;line-height:15px;padding:5px 10px 0;position:absolute;right:0;text-align:right;top:0} -------------------------------------------------------------------------------- /docs/_res/emoji.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | var AllGithubEmoji = [ 3 | '+1', 4 | '100', 5 | '1234', 6 | '8ball', 7 | 'a', 8 | 'ab', 9 | 'abc', 10 | 'abcd', 11 | 'accept', 12 | 'aerial_tramway', 13 | 'airplane', 14 | 'alarm_clock', 15 | 'alien', 16 | 'ambulance', 17 | 'anchor', 18 | 'angel', 19 | 'anger', 20 | 'angry', 21 | 'anguished', 22 | 'ant', 23 | 'apple', 24 | 'aquarius', 25 | 'aries', 26 | 'arrow_backward', 27 | 'arrow_double_down', 28 | 'arrow_double_up', 29 | 'arrow_down', 30 | 'arrow_down_small', 31 | 'arrow_forward', 32 | 'arrow_heading_down', 33 | 'arrow_heading_up', 34 | 'arrow_left', 35 | 'arrow_lower_left', 36 | 'arrow_lower_right', 37 | 'arrow_right', 38 | 'arrow_right_hook', 39 | 'arrow_up', 40 | 'arrow_up_down', 41 | 'arrow_up_small', 42 | 'arrow_upper_left', 43 | 'arrow_upper_right', 44 | 'arrows_clockwise', 45 | 'arrows_counterclockwise', 46 | 'art', 47 | 'articulated_lorry', 48 | 'astonished', 49 | 'athletic_shoe', 50 | 'atm', 51 | 'b', 52 | 'baby', 53 | 'baby_bottle', 54 | 'baby_chick', 55 | 'baby_symbol', 56 | 'back', 57 | 'baggage_claim', 58 | 'balloon', 59 | 'ballot_box_with_check', 60 | 'bamboo', 61 | 'banana', 62 | 'bangbang', 63 | 'bank', 64 | 'bar_chart', 65 | 'barber', 66 | 'baseball', 67 | 'basketball', 68 | 'bath', 69 | 'bathtub', 70 | 'battery', 71 | 'bear', 72 | 'bee', 73 | 'beer', 74 | 'beers', 75 | 'beetle', 76 | 'beginner', 77 | 'bell', 78 | 'bento', 79 | 'bicyclist', 80 | 'bike', 81 | 'bikini', 82 | 'bird', 83 | 'birthday', 84 | 'black_circle', 85 | 'black_joker', 86 | 'black_large_square', 87 | 'black_medium_small_square', 88 | 'black_medium_square', 89 | 'black_nib', 90 | 'black_small_square', 91 | 'black_square_button', 92 | 'blossom', 93 | 'blowfish', 94 | 'blue_book', 95 | 'blue_car', 96 | 'blue_heart', 97 | 'blush', 98 | 'boar', 99 | 'boat', 100 | 'bomb', 101 | 'book', 102 | 'bookmark', 103 | 'bookmark_tabs', 104 | 'books', 105 | 'boom', 106 | 'boot', 107 | 'bouquet', 108 | 'bow', 109 | 'bowling', 110 | 'bowtie', 111 | 'boy', 112 | 'bread', 113 | 'bride_with_veil', 114 | 'bridge_at_night', 115 | 'briefcase', 116 | 'broken_heart', 117 | 'bug', 118 | 'bulb', 119 | 'bullettrain_front', 120 | 'bullettrain_side', 121 | 'bus', 122 | 'busstop', 123 | 'bust_in_silhouette', 124 | 'busts_in_silhouette', 125 | 'cactus', 126 | 'cake', 127 | 'calendar', 128 | 'calling', 129 | 'camel', 130 | 'camera', 131 | 'cancer', 132 | 'candy', 133 | 'capital_abcd', 134 | 'capricorn', 135 | 'car', 136 | 'card_index', 137 | 'carousel_horse', 138 | 'cat', 139 | 'cat2', 140 | 'cd', 141 | 'chart', 142 | 'chart_with_downwards_trend', 143 | 'chart_with_upwards_trend', 144 | 'checkered_flag', 145 | 'cherries', 146 | 'cherry_blossom', 147 | 'chestnut', 148 | 'chicken', 149 | 'children_crossing', 150 | 'chocolate_bar', 151 | 'christmas_tree', 152 | 'church', 153 | 'cinema', 154 | 'circus_tent', 155 | 'city_sunrise', 156 | 'city_sunset', 157 | 'cl', 158 | 'clap', 159 | 'clapper', 160 | 'clipboard', 161 | 'clock1', 162 | 'clock10', 163 | 'clock1030', 164 | 'clock11', 165 | 'clock1130', 166 | 'clock12', 167 | 'clock1230', 168 | 'clock130', 169 | 'clock2', 170 | 'clock230', 171 | 'clock3', 172 | 'clock330', 173 | 'clock4', 174 | 'clock430', 175 | 'clock5', 176 | 'clock530', 177 | 'clock6', 178 | 'clock630', 179 | 'clock7', 180 | 'clock730', 181 | 'clock8', 182 | 'clock830', 183 | 'clock9', 184 | 'clock930', 185 | 'closed_book', 186 | 'closed_lock_with_key', 187 | 'closed_umbrella', 188 | 'cloud', 189 | 'clubs', 190 | 'cn', 191 | 'cocktail', 192 | 'coffee', 193 | 'cold_sweat', 194 | 'collision', 195 | 'computer', 196 | 'confetti_ball', 197 | 'confounded', 198 | 'confused', 199 | 'congratulations', 200 | 'construction', 201 | 'construction_worker', 202 | 'convenience_store', 203 | 'cookie', 204 | 'cool', 205 | 'cop', 206 | 'copyright', 207 | 'corn', 208 | 'couple', 209 | 'couple_with_heart', 210 | 'couplekiss', 211 | 'cow', 212 | 'cow2', 213 | 'credit_card', 214 | 'crescent_moon', 215 | 'crocodile', 216 | 'crossed_flags', 217 | 'crown', 218 | 'cry', 219 | 'crying_cat_face', 220 | 'crystal_ball', 221 | 'cupid', 222 | 'curly_loop', 223 | 'currency_exchange', 224 | 'curry', 225 | 'custard', 226 | 'customs', 227 | 'cyclone', 228 | 'dancer', 229 | 'dancers', 230 | 'dango', 231 | 'dart', 232 | 'dash', 233 | 'date', 234 | 'de', 235 | 'deciduous_tree', 236 | 'department_store', 237 | 'diamond_shape_with_a_dot_inside', 238 | 'diamonds', 239 | 'disappointed', 240 | 'disappointed_relieved', 241 | 'dizzy', 242 | 'dizzy_face', 243 | 'do_not_litter', 244 | 'dog', 245 | 'dog2', 246 | 'dollar', 247 | 'dolls', 248 | 'dolphin', 249 | 'door', 250 | 'doughnut', 251 | 'dragon', 252 | 'dragon_face', 253 | 'dress', 254 | 'dromedary_camel', 255 | 'droplet', 256 | 'dvd', 257 | 'e-mail', 258 | 'ear', 259 | 'ear_of_rice', 260 | 'earth_africa', 261 | 'earth_americas', 262 | 'earth_asia', 263 | 'egg', 264 | 'eggplant', 265 | 'eight', 266 | 'eight_pointed_black_star', 267 | 'eight_spoked_asterisk', 268 | 'electric_plug', 269 | 'elephant', 270 | 'email', 271 | 'end', 272 | 'envelope', 273 | 'envelope_with_arrow', 274 | 'es', 275 | 'euro', 276 | 'european_castle', 277 | 'european_post_office', 278 | 'evergreen_tree', 279 | 'exclamation', 280 | 'expressionless', 281 | 'eyeglasses', 282 | 'eyes', 283 | 'facepunch', 284 | 'factory', 285 | 'fallen_leaf', 286 | 'family', 287 | 'fast_forward', 288 | 'fax', 289 | 'fearful', 290 | 'feelsgood', 291 | 'feet', 292 | 'ferris_wheel', 293 | 'file_folder', 294 | 'finnadie', 295 | 'fire', 296 | 'fire_engine', 297 | 'fireworks', 298 | 'first_quarter_moon', 299 | 'first_quarter_moon_with_face', 300 | 'fish', 301 | 'fish_cake', 302 | 'fishing_pole_and_fish', 303 | 'fist', 304 | 'five', 305 | 'flags', 306 | 'flashlight', 307 | 'flipper', 308 | 'floppy_disk', 309 | 'flower_playing_cards', 310 | 'flushed', 311 | 'foggy', 312 | 'football', 313 | 'footprints', 314 | 'fork_and_knife', 315 | 'fountain', 316 | 'four', 317 | 'four_leaf_clover', 318 | 'fr', 319 | 'free', 320 | 'fried_shrimp', 321 | 'fries', 322 | 'frog', 323 | 'frowning', 324 | 'fu', 325 | 'fuelpump', 326 | 'full_moon', 327 | 'full_moon_with_face', 328 | 'game_die', 329 | 'gb', 330 | 'gem', 331 | 'gemini', 332 | 'ghost', 333 | 'gift', 334 | 'gift_heart', 335 | 'girl', 336 | 'globe_with_meridians', 337 | 'goat', 338 | 'goberserk', 339 | 'godmode', 340 | 'golf', 341 | 'grapes', 342 | 'green_apple', 343 | 'green_book', 344 | 'green_heart', 345 | 'grey_exclamation', 346 | 'grey_question', 347 | 'grimacing', 348 | 'grin', 349 | 'grinning', 350 | 'guardsman', 351 | 'guitar', 352 | 'gun', 353 | 'haircut', 354 | 'hamburger', 355 | 'hammer', 356 | 'hamster', 357 | 'hand', 358 | 'handbag', 359 | 'hankey', 360 | 'hash', 361 | 'hatched_chick', 362 | 'hatching_chick', 363 | 'headphones', 364 | 'hear_no_evil', 365 | 'heart', 366 | 'heart_decoration', 367 | 'heart_eyes', 368 | 'heart_eyes_cat', 369 | 'heartbeat', 370 | 'heartpulse', 371 | 'hearts', 372 | 'heavy_check_mark', 373 | 'heavy_division_sign', 374 | 'heavy_dollar_sign', 375 | 'heavy_exclamation_mark', 376 | 'heavy_minus_sign', 377 | 'heavy_multiplication_x', 378 | 'heavy_plus_sign', 379 | 'helicopter', 380 | 'herb', 381 | 'hibiscus', 382 | 'high_brightness', 383 | 'high_heel', 384 | 'hocho', 385 | 'honey_pot', 386 | 'honeybee', 387 | 'horse', 388 | 'horse_racing', 389 | 'hospital', 390 | 'hotel', 391 | 'hotsprings', 392 | 'hourglass', 393 | 'hourglass_flowing_sand', 394 | 'house', 395 | 'house_with_garden', 396 | 'hurtrealbad', 397 | 'hushed', 398 | 'ice_cream', 399 | 'icecream', 400 | 'id', 401 | 'ideograph_advantage', 402 | 'imp', 403 | 'inbox_tray', 404 | 'incoming_envelope', 405 | 'information_desk_person', 406 | 'information_source', 407 | 'innocent', 408 | 'interrobang', 409 | 'iphone', 410 | 'it', 411 | 'izakaya_lantern', 412 | 'jack_o_lantern', 413 | 'japan', 414 | 'japanese_castle', 415 | 'japanese_goblin', 416 | 'japanese_ogre', 417 | 'jeans', 418 | 'joy', 419 | 'joy_cat', 420 | 'jp', 421 | 'key', 422 | 'keycap_ten', 423 | 'kimono', 424 | 'kiss', 425 | 'kissing', 426 | 'kissing_cat', 427 | 'kissing_closed_eyes', 428 | 'kissing_heart', 429 | 'kissing_smiling_eyes', 430 | 'koala', 431 | 'koko', 432 | 'kr', 433 | 'lantern', 434 | 'large_blue_circle', 435 | 'large_blue_diamond', 436 | 'large_orange_diamond', 437 | 'last_quarter_moon', 438 | 'last_quarter_moon_with_face', 439 | 'laughing', 440 | 'leaves', 441 | 'ledger', 442 | 'left_luggage', 443 | 'left_right_arrow', 444 | 'leftwards_arrow_with_hook', 445 | 'lemon', 446 | 'leo', 447 | 'leopard', 448 | 'libra', 449 | 'light_rail', 450 | 'link', 451 | 'lips', 452 | 'lipstick', 453 | 'lock', 454 | 'lock_with_ink_pen', 455 | 'lollipop', 456 | 'loop', 457 | 'loud_sound', 458 | 'loudspeaker', 459 | 'love_hotel', 460 | 'love_letter', 461 | 'low_brightness', 462 | 'm', 463 | 'mag', 464 | 'mag_right', 465 | 'mahjong', 466 | 'mailbox', 467 | 'mailbox_closed', 468 | 'mailbox_with_mail', 469 | 'mailbox_with_no_mail', 470 | 'man', 471 | 'man_with_gua_pi_mao', 472 | 'man_with_turban', 473 | 'mans_shoe', 474 | 'maple_leaf', 475 | 'mask', 476 | 'massage', 477 | 'meat_on_bone', 478 | 'mega', 479 | 'melon', 480 | 'memo', 481 | 'mens', 482 | 'metal', 483 | 'metro', 484 | 'microphone', 485 | 'microscope', 486 | 'milky_way', 487 | 'minibus', 488 | 'minidisc', 489 | 'mobile_phone_off', 490 | 'money_with_wings', 491 | 'moneybag', 492 | 'monkey', 493 | 'monkey_face', 494 | 'monorail', 495 | 'moon', 496 | 'mortar_board', 497 | 'mount_fuji', 498 | 'mountain_bicyclist', 499 | 'mountain_cableway', 500 | 'mountain_railway', 501 | 'mouse', 502 | 'mouse2', 503 | 'movie_camera', 504 | 'moyai', 505 | 'muscle', 506 | 'mushroom', 507 | 'musical_keyboard', 508 | 'musical_note', 509 | 'musical_score', 510 | 'mute', 511 | 'nail_care', 512 | 'name_badge', 513 | 'neckbeard', 514 | 'necktie', 515 | 'negative_squared_cross_mark', 516 | 'neutral_face', 517 | 'new', 518 | 'new_moon', 519 | 'new_moon_with_face', 520 | 'newspaper', 521 | 'ng', 522 | 'night_with_stars', 523 | 'nine', 524 | 'no_bell', 525 | 'no_bicycles', 526 | 'no_entry', 527 | 'no_entry_sign', 528 | 'no_good', 529 | 'no_mobile_phones', 530 | 'no_mouth', 531 | 'no_pedestrians', 532 | 'no_smoking', 533 | 'non-potable_water', 534 | 'nose', 535 | 'notebook', 536 | 'notebook_with_decorative_cover', 537 | 'notes', 538 | 'nut_and_bolt', 539 | 'o', 540 | 'o2', 541 | 'ocean', 542 | 'octocat', 543 | 'octopus', 544 | 'oden', 545 | 'office', 546 | 'ok', 547 | 'ok_hand', 548 | 'ok_woman', 549 | 'older_man', 550 | 'older_woman', 551 | 'on', 552 | 'oncoming_automobile', 553 | 'oncoming_bus', 554 | 'oncoming_police_car', 555 | 'oncoming_taxi', 556 | 'one', 557 | 'open_book', 558 | 'open_file_folder', 559 | 'open_hands', 560 | 'open_mouth', 561 | 'ophiuchus', 562 | 'orange_book', 563 | 'outbox_tray', 564 | 'ox', 565 | 'package', 566 | 'page_facing_up', 567 | 'page_with_curl', 568 | 'pager', 569 | 'palm_tree', 570 | 'panda_face', 571 | 'paperclip', 572 | 'parking', 573 | 'part_alternation_mark', 574 | 'partly_sunny', 575 | 'passport_control', 576 | 'paw_prints', 577 | 'peach', 578 | 'pear', 579 | 'pencil', 580 | 'pencil2', 581 | 'penguin', 582 | 'pensive', 583 | 'performing_arts', 584 | 'persevere', 585 | 'person_frowning', 586 | 'person_with_blond_hair', 587 | 'person_with_pouting_face', 588 | 'phone', 589 | 'pig', 590 | 'pig2', 591 | 'pig_nose', 592 | 'pill', 593 | 'pineapple', 594 | 'pisces', 595 | 'pizza', 596 | 'point_down', 597 | 'point_left', 598 | 'point_right', 599 | 'point_up', 600 | 'point_up_2', 601 | 'police_car', 602 | 'poodle', 603 | 'poop', 604 | 'post_office', 605 | 'postal_horn', 606 | 'postbox', 607 | 'potable_water', 608 | 'pouch', 609 | 'poultry_leg', 610 | 'pound', 611 | 'pouting_cat', 612 | 'pray', 613 | 'princess', 614 | 'punch', 615 | 'purple_heart', 616 | 'purse', 617 | 'pushpin', 618 | 'put_litter_in_its_place', 619 | 'question', 620 | 'rabbit', 621 | 'rabbit2', 622 | 'racehorse', 623 | 'radio', 624 | 'radio_button', 625 | 'rage', 626 | 'rage1', 627 | 'rage2', 628 | 'rage3', 629 | 'rage4', 630 | 'railway_car', 631 | 'rainbow', 632 | 'raised_hand', 633 | 'raised_hands', 634 | 'raising_hand', 635 | 'ram', 636 | 'ramen', 637 | 'rat', 638 | 'recycle', 639 | 'red_car', 640 | 'red_circle', 641 | 'registered', 642 | 'relaxed', 643 | 'relieved', 644 | 'repeat', 645 | 'repeat_one', 646 | 'restroom', 647 | 'revolving_hearts', 648 | 'rewind', 649 | 'ribbon', 650 | 'rice', 651 | 'rice_ball', 652 | 'rice_cracker', 653 | 'rice_scene', 654 | 'ring', 655 | 'rocket', 656 | 'roller_coaster', 657 | 'rooster', 658 | 'rose', 659 | 'rotating_light', 660 | 'round_pushpin', 661 | 'rowboat', 662 | 'ru', 663 | 'rugby_football', 664 | 'runner', 665 | 'running', 666 | 'running_shirt_with_sash', 667 | 'sa', 668 | 'sagittarius', 669 | 'sailboat', 670 | 'sake', 671 | 'sandal', 672 | 'santa', 673 | 'satellite', 674 | 'satisfied', 675 | 'saxophone', 676 | 'school', 677 | 'school_satchel', 678 | 'scissors', 679 | 'scorpius', 680 | 'scream', 681 | 'scream_cat', 682 | 'scroll', 683 | 'seat', 684 | 'secret', 685 | 'see_no_evil', 686 | 'seedling', 687 | 'seven', 688 | 'shaved_ice', 689 | 'sheep', 690 | 'shell', 691 | 'ship', 692 | 'shipit', 693 | 'shirt', 694 | 'shit', 695 | 'shoe', 696 | 'shower', 697 | 'signal_strength', 698 | 'six', 699 | 'six_pointed_star', 700 | 'ski', 701 | 'skull', 702 | 'sleeping', 703 | 'sleepy', 704 | 'slot_machine', 705 | 'small_blue_diamond', 706 | 'small_orange_diamond', 707 | 'small_red_triangle', 708 | 'small_red_triangle_down', 709 | 'smile', 710 | 'smile_cat', 711 | 'smiley', 712 | 'smiley_cat', 713 | 'smiling_imp', 714 | 'smirk', 715 | 'smirk_cat', 716 | 'smoking', 717 | 'snail', 718 | 'snake', 719 | 'snowboarder', 720 | 'snowflake', 721 | 'snowman', 722 | 'sob', 723 | 'soccer', 724 | 'soon', 725 | 'sos', 726 | 'sound', 727 | 'space_invader', 728 | 'spades', 729 | 'spaghetti', 730 | 'sparkle', 731 | 'sparkler', 732 | 'sparkles', 733 | 'sparkling_heart', 734 | 'speak_no_evil', 735 | 'speaker', 736 | 'speech_balloon', 737 | 'speedboat', 738 | 'squirrel', 739 | 'star', 740 | 'star2', 741 | 'stars', 742 | 'station', 743 | 'statue_of_liberty', 744 | 'steam_locomotive', 745 | 'stew', 746 | 'straight_ruler', 747 | 'strawberry', 748 | 'stuck_out_tongue', 749 | 'stuck_out_tongue_closed_eyes', 750 | 'stuck_out_tongue_winking_eye', 751 | 'sun_with_face', 752 | 'sunflower', 753 | 'sunglasses', 754 | 'sunny', 755 | 'sunrise', 756 | 'sunrise_over_mountains', 757 | 'surfer', 758 | 'sushi', 759 | 'suspect', 760 | 'suspension_railway', 761 | 'sweat', 762 | 'sweat_drops', 763 | 'sweat_smile', 764 | 'sweet_potato', 765 | 'swimmer', 766 | 'symbols', 767 | 'syringe', 768 | 'tada', 769 | 'tanabata_tree', 770 | 'tangerine', 771 | 'taurus', 772 | 'taxi', 773 | 'tea', 774 | 'telephone', 775 | 'telephone_receiver', 776 | 'telescope', 777 | 'tennis', 778 | 'tent', 779 | 'thought_balloon', 780 | 'three', 781 | 'thumbsdown', 782 | 'thumbsup', 783 | 'ticket', 784 | 'tiger', 785 | 'tiger2', 786 | 'tired_face', 787 | 'tm', 788 | 'toilet', 789 | 'tokyo_tower', 790 | 'tomato', 791 | 'tongue', 792 | 'top', 793 | 'tophat', 794 | 'tractor', 795 | 'traffic_light', 796 | 'train', 797 | 'train2', 798 | 'tram', 799 | 'triangular_flag_on_post', 800 | 'triangular_ruler', 801 | 'trident', 802 | 'triumph', 803 | 'trolleybus', 804 | 'trollface', 805 | 'trophy', 806 | 'tropical_drink', 807 | 'tropical_fish', 808 | 'truck', 809 | 'trumpet', 810 | 'tshirt', 811 | 'tulip', 812 | 'turtle', 813 | 'tv', 814 | 'twisted_rightwards_arrows', 815 | 'two', 816 | 'two_hearts', 817 | 'two_men_holding_hands', 818 | 'two_women_holding_hands', 819 | 'u5272', 820 | 'u5408', 821 | 'u55b6', 822 | 'u6307', 823 | 'u6708', 824 | 'u6709', 825 | 'u6e80', 826 | 'u7121', 827 | 'u7533', 828 | 'u7981', 829 | 'u7a7a', 830 | 'uk', 831 | 'umbrella', 832 | 'unamused', 833 | 'underage', 834 | 'unlock', 835 | 'up', 836 | 'us', 837 | 'v', 838 | 'vertical_traffic_light', 839 | 'vhs', 840 | 'vibration_mode', 841 | 'video_camera', 842 | 'video_game', 843 | 'violin', 844 | 'virgo', 845 | 'volcano', 846 | 'vs', 847 | 'walking', 848 | 'waning_crescent_moon', 849 | 'waning_gibbous_moon', 850 | 'warning', 851 | 'watch', 852 | 'water_buffalo', 853 | 'watermelon', 854 | 'wave', 855 | 'wavy_dash', 856 | 'waxing_crescent_moon', 857 | 'waxing_gibbous_moon', 858 | 'wc', 859 | 'weary', 860 | 'wedding', 861 | 'whale', 862 | 'whale2', 863 | 'wheelchair', 864 | 'white_check_mark', 865 | 'white_circle', 866 | 'white_flower', 867 | 'white_large_square', 868 | 'white_medium_small_square', 869 | 'white_medium_square', 870 | 'white_small_square', 871 | 'white_square_button', 872 | 'wind_chime', 873 | 'wine_glass', 874 | 'wink', 875 | 'wolf', 876 | 'woman', 877 | 'womans_clothes', 878 | 'womans_hat', 879 | 'womens', 880 | 'worried', 881 | 'wrench', 882 | 'x', 883 | 'yellow_heart', 884 | 'yen', 885 | 'yum', 886 | 'zap', 887 | 'zero', 888 | 'zzz' 889 | ]; 890 | 891 | // Emoji from All-Github-Emoji-Icons 892 | // https://github.com/scotch-io/All-Github-Emoji-Icons 893 | window.emojify = function (match, $1) { 894 | return AllGithubEmoji.indexOf($1) === -1 ? 895 | match : 896 | '' +
899 |       $1 +
900 |       '' 901 | }; 902 | 903 | }()); -------------------------------------------------------------------------------- /NORM/src/NOrmQuerySet.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "NOrm.h" 5 | #include "NOrm_p.h" 6 | #include "NOrmQuerySet.h" 7 | #include "NOrmWhere_p.h" 8 | 9 | NOrmCompiler::NOrmCompiler(const char* modelName, const QSqlDatabase& db) { 10 | driver = db.driver(); 11 | baseModel = NOrm::metaModel(modelName); 12 | } 13 | 14 | QString NOrmCompiler::referenceModel(const QString& modelPath, NOrmMetaModel* metaModel, bool nullable) { 15 | if (modelPath.isEmpty()) 16 | return driver->escapeIdentifier(baseModel.table(), QSqlDriver::TableName); 17 | 18 | if (modelRefs.contains(modelPath)) 19 | return modelRefs.value(modelPath).tableReference; 20 | 21 | const QString modelRef = QLatin1String("T") + QString::number(modelRefs.size()); 22 | modelRefs.insert(modelPath, NOrmModelReference(modelRef, *metaModel, nullable)); 23 | return modelRef; 24 | } 25 | 26 | void NOrmCompiler::limitSql(QString &limit, int lowMark, int highMark) 27 | { 28 | NOrmDatabase::DatabaseType databaseType = NOrmDatabase::databaseType(NOrm::database()); 29 | 30 | switch (databaseType) { 31 | case NOrmDatabase::UnknownDB: 32 | case NOrmDatabase::MySqlServer: 33 | case NOrmDatabase::PostgreSQL: 34 | case NOrmDatabase::Oracle: 35 | case NOrmDatabase::Sybase: 36 | case NOrmDatabase::SQLite: 37 | case NOrmDatabase::Interbase: 38 | case NOrmDatabase::DB2: 39 | case NOrmDatabase::DaMeng: 40 | if (highMark > 0) { 41 | if (lowMark > 0) { 42 | limit += QString(" LIMIT ") + QString::number(lowMark); 43 | limit += QString(", ") + QString::number(highMark-lowMark); 44 | } else { 45 | limit += QString(" LIMIT ") + QString::number(highMark); 46 | } 47 | } else if(highMark == 0) { 48 | if (lowMark > 0) { 49 | limit += QString(" LIMIT ") + QString::number(lowMark); 50 | } 51 | } 52 | break; 53 | case NOrmDatabase::MSSqlServer: 54 | if (limit.isEmpty() && (highMark > 0 || lowMark > 0)) 55 | limit += QLatin1String(" ORDER BY ") + databaseColumn(baseModel.primaryKey()); 56 | 57 | if (lowMark > 0 || (lowMark == 0 && highMark > 0)) { 58 | limit += QLatin1String(" OFFSET ") + QString::number(lowMark); 59 | limit += QLatin1String(" ROWS"); 60 | } 61 | 62 | if (highMark > 0) 63 | limit += QString(" FETCH NEXT %1 ROWS ONLY").arg(highMark - lowMark); 64 | break; 65 | } 66 | } 67 | 68 | QString NOrmCompiler::databaseColumn(const QString& name) { 69 | NOrmMetaModel model = baseModel; 70 | QString modelName; 71 | QString modelPath; 72 | QString modelRef = referenceModel(QString(), &model, false); 73 | QStringList bits = name.split(QLatin1String("__")); 74 | 75 | while (bits.size() > 1) { 76 | const QByteArray fk = bits.first().toLatin1(); 77 | NOrmMetaModel foreignModel; 78 | bool foreignNullable = false; 79 | 80 | if (!modelPath.isEmpty()) 81 | modelPath += QLatin1String("__"); 82 | modelPath += bits.first(); 83 | 84 | if (!model.foreignFields().contains(fk)) { 85 | // this might be a reverse relation, so look for the model 86 | // and if it exists continue 87 | foreignModel = NOrm::metaModel(fk); 88 | NOrmReverseReference rev; 89 | const QMap foreignFields = foreignModel.foreignFields(); 90 | foreach (const QByteArray& foreignKey, foreignFields.keys()) { 91 | if (foreignFields[foreignKey] == baseModel.className()) { 92 | rev.leftHandKey = foreignModel.localField(foreignKey + "_id").column(); 93 | break; 94 | } 95 | } 96 | 97 | if (rev.leftHandKey.isEmpty()) { 98 | qWarning() << "Invalid field lookup" << name; 99 | return QString(); 100 | } 101 | rev.rightHandKey = foreignModel.primaryKey(); 102 | reverseModelRefs[modelPath] = rev; 103 | } else { 104 | foreignModel = NOrm::metaModel(model.foreignFields()[fk]); 105 | foreignNullable = model.localField(fk + QByteArray("_id")).isNullable(); 106 | ; 107 | } 108 | 109 | // store reference 110 | modelRef = referenceModel(modelPath, &foreignModel, foreignNullable); 111 | modelName = fk; 112 | 113 | model = foreignModel; 114 | bits.takeFirst(); 115 | } 116 | 117 | const NOrmMetaField field = model.localField(bits.join(QLatin1String("__")).toLatin1()); 118 | return modelRef + QLatin1Char('.') + driver->escapeIdentifier(field.column(), QSqlDriver::FieldName); 119 | } 120 | 121 | QStringList NOrmCompiler::fieldNames(bool recurse, 122 | const QStringList* fields, 123 | NOrmMetaModel* metaModel, 124 | const QString& modelPath, 125 | bool nullable) { 126 | QStringList columns; 127 | if (!metaModel) 128 | metaModel = &baseModel; 129 | 130 | // store reference 131 | const QString tableName = referenceModel(modelPath, metaModel, nullable); 132 | foreach (const NOrmMetaField& field, metaModel->localFields()) 133 | columns << tableName + QLatin1Char('.') + driver->escapeIdentifier(field.column(), QSqlDriver::FieldName); 134 | if (!recurse) 135 | return columns; 136 | 137 | // recurse for foreign keys 138 | const QString pathPrefix = modelPath.isEmpty() ? QString() : (modelPath + QLatin1String("__")); 139 | foreach (const QByteArray& fkName, metaModel->foreignFields().keys()) { 140 | NOrmMetaModel metaForeign = NOrm::metaModel(metaModel->foreignFields()[fkName]); 141 | bool nullableForeign = metaModel->localField(fkName + QByteArray("_id")).isNullable(); 142 | QString fkS(fkName); 143 | if ((fields != nullptr) && (fields->contains(fkS))) { 144 | QStringList nsl = fields->filter(QRegExp("^" + fkS + "__")).replaceInStrings(QRegExp("^" + fkS + "__"), ""); 145 | columns += 146 | fieldNames(recurse, &nsl, &metaForeign, pathPrefix + QString::fromLatin1(fkName), nullableForeign); 147 | } 148 | 149 | if (fields == nullptr) { 150 | columns += fieldNames(recurse, nullptr, &metaForeign, pathPrefix + QString::fromLatin1(fkName), nullableForeign); 151 | } 152 | } 153 | return columns; 154 | } 155 | 156 | QString NOrmCompiler::fromSql() { 157 | QString from = driver->escapeIdentifier(baseModel.table(), QSqlDriver::TableName); 158 | foreach (const QString& name, modelRefs.keys()) { 159 | const NOrmModelReference& ref = modelRefs[name]; 160 | 161 | QString leftHandColumn, rightHandColumn; 162 | if (reverseModelRefs.contains(name)) { 163 | const NOrmReverseReference& rev = reverseModelRefs[name]; 164 | leftHandColumn = 165 | ref.tableReference + "." + driver->escapeIdentifier(rev.leftHandKey, QSqlDriver::FieldName); 166 | ; 167 | rightHandColumn = databaseColumn(rev.rightHandKey); 168 | } else { 169 | leftHandColumn = databaseColumn(name + QLatin1String("__pk")); 170 | rightHandColumn = databaseColumn(name + QLatin1String("_id")); 171 | } 172 | from += QString::fromLatin1(" %1 %2 %3 ON %4 = %5") 173 | .arg(ref.nullable ? "LEFT OUTER JOIN" : "INNER JOIN") 174 | .arg(driver->escapeIdentifier(ref.metaModel.table(), QSqlDriver::TableName)) 175 | .arg(ref.tableReference) 176 | .arg(leftHandColumn) 177 | .arg(rightHandColumn); 178 | } 179 | return from; 180 | } 181 | 182 | QString NOrmCompiler::orderLimitSql(const QStringList& orderBy, int lowMark, int highMark) { 183 | QString limit; 184 | 185 | // order 186 | QStringList bits; 187 | QString field; 188 | foreach (field, orderBy) { 189 | QString order = QLatin1String("ASC"); 190 | if (field.startsWith(QLatin1Char('-'))) { 191 | order = QLatin1String("DESC"); 192 | field = field.mid(1); 193 | } else if (field.startsWith(QLatin1Char('+'))) { 194 | field = field.mid(1); 195 | } 196 | bits.append(databaseColumn(field) + QLatin1Char(' ') + order); 197 | } 198 | 199 | if (!bits.isEmpty()) 200 | limit += QLatin1String(" ORDER BY ") + bits.join(QLatin1String(", ")); 201 | 202 | // limits 203 | limitSql(limit, lowMark, highMark); 204 | 205 | return limit; 206 | } 207 | 208 | void NOrmCompiler::resolve(NOrmWhere& where) { 209 | // resolve column 210 | if (where.d->operation != NOrmWhere::None) 211 | where.d->key = databaseColumn(where.d->key); 212 | 213 | // recurse into children 214 | for (int i = 0; i < where.d->children.size(); i++) 215 | resolve(where.d->children[i]); 216 | } 217 | 218 | NOrmQuerySetPrivate::NOrmQuerySetPrivate(const char* modelName) 219 | : counter(1), hasResults(false), lowMark(0), highMark(0), selectRelated(false), m_modelName(modelName) {} 220 | 221 | void NOrmQuerySetPrivate::addFilter(const NOrmWhere& where) { 222 | // it is not possible to add filters once a limit has been set 223 | Q_ASSERT(!lowMark && !highMark); 224 | 225 | whereClause = whereClause && where; 226 | } 227 | 228 | NOrmWhere NOrmQuerySetPrivate::resolvedWhere(const QSqlDatabase& db) const { 229 | NOrmCompiler compiler(m_modelName, db); 230 | NOrmWhere resolvedWhere(whereClause); 231 | compiler.resolve(resolvedWhere); 232 | return resolvedWhere; 233 | } 234 | 235 | bool NOrmQuerySetPrivate::sqlDelete() { 236 | // DELETE on an empty queryset doesn't need a query 237 | if (whereClause.isNone()) 238 | return true; 239 | 240 | // FIXME : it is not possible to remove entries once a limit has been set 241 | // because SQLite does not support limits on DELETE unless compiled with the 242 | // SQLITE_ENABLE_UPDATE_DELETE_LIMIT option 243 | if (lowMark || highMark) 244 | return false; 245 | 246 | // execute query 247 | NOrmQuery query(deleteQuery()); 248 | if (!query.exec()) 249 | return false; 250 | 251 | // invalidate cache 252 | if (hasResults) { 253 | properties.clear(); 254 | hasResults = false; 255 | } 256 | return true; 257 | } 258 | 259 | bool NOrmQuerySetPrivate::sqlFetch() { 260 | if (hasResults || whereClause.isNone()) 261 | return true; 262 | 263 | // execute query 264 | NOrmQuery query(selectQuery()); 265 | if (!query.exec()) 266 | return false; 267 | 268 | // store results 269 | while (query.next()) { 270 | QVariantList props; 271 | const int propCount = query.record().count(); 272 | for (int i = 0; i < propCount; ++i) 273 | props << query.value(i); 274 | properties.append(props); 275 | } 276 | hasResults = true; 277 | return true; 278 | } 279 | 280 | bool NOrmQuerySetPrivate::sqlInsert(const QVariantMap& fields, QVariant* insertId) { 281 | // execute query 282 | NOrmQuery query(insertQuery(fields)); 283 | if (!query.exec()) 284 | return false; 285 | 286 | // fetch autoincrement pk 287 | if (insertId) { 288 | QSqlDatabase db = NOrm::database(); 289 | NOrmDatabase::DatabaseType databaseType = NOrmDatabase::databaseType(db); 290 | 291 | if (databaseType == NOrmDatabase::PostgreSQL) { 292 | const NOrmMetaModel metaModel = NOrm::metaModel(m_modelName); 293 | NOrmQuery query(db); 294 | const NOrmMetaField primaryKey = metaModel.localField("pk"); 295 | const QString seqName = db.driver()->escapeIdentifier( 296 | metaModel.table() + QLatin1Char('_') + primaryKey.column() + QLatin1String("_seq"), 297 | QSqlDriver::FieldName); 298 | if (!query.exec(QLatin1String("SELECT CURRVAL('") + seqName + QLatin1String("')")) || !query.next()) 299 | return false; 300 | *insertId = query.value(0); 301 | } else if (databaseType == NOrmDatabase::DaMeng) { 302 | const NOrmMetaModel metaModel = NOrm::metaModel(m_modelName); 303 | NOrmQuery query(db); 304 | if (!query.exec(QLatin1String("SELECT IDENT_CURRENT('") + metaModel.table() + QLatin1String("')")) || !query.next()) 305 | return false; 306 | *insertId = query.value(0); 307 | } else { 308 | *insertId = query.lastInsertId(); 309 | } 310 | } 311 | 312 | // invalidate cache 313 | if (hasResults) { 314 | properties.clear(); 315 | hasResults = false; 316 | } 317 | 318 | return true; 319 | } 320 | 321 | bool NOrmQuerySetPrivate::sqlLoad(QObject* model, int index) { 322 | if (!sqlFetch()) 323 | return false; 324 | 325 | if (index < 0 || index >= properties.size()) { 326 | qWarning("NOrmQuerySet out of bounds"); 327 | return false; 328 | } 329 | 330 | const NOrmMetaModel metaModel = NOrm::metaModel(m_modelName); 331 | int pos = 0; 332 | metaModel.load(model, properties.at(index), pos, this->relatedFields); 333 | return true; 334 | } 335 | 336 | static QString aggregationToString(NOrmWhere::AggregateType type) { 337 | switch (type) { 338 | case NOrmWhere::AVG: 339 | return QLatin1String("AVG"); 340 | case NOrmWhere::COUNT: 341 | return QLatin1String("COUNT"); 342 | case NOrmWhere::SUM: 343 | return QLatin1String("SUM"); 344 | case NOrmWhere::MIN: 345 | return QLatin1String("MIN"); 346 | case NOrmWhere::MAX: 347 | return QLatin1String("MAX"); 348 | } 349 | return QString(); 350 | } 351 | 352 | NOrmQuery NOrmQuerySetPrivate::aggregateQuery(const NOrmWhere::AggregateType func, const QString& field) const { 353 | QSqlDatabase db = NOrm::database(); 354 | 355 | // build query 356 | NOrmCompiler compiler(m_modelName, db); 357 | NOrmWhere resolvedWhere(whereClause); 358 | compiler.resolve(resolvedWhere); 359 | 360 | const QString where = resolvedWhere.sql(db); 361 | const QString limit = compiler.orderLimitSql(QStringList(), lowMark, highMark); 362 | 363 | QString sql = 364 | QLatin1String("SELECT ") + aggregationToString(func) + "(" + field + ") " + "FROM " + compiler.fromSql(); 365 | if (!where.isEmpty()) 366 | sql += QLatin1String(" WHERE ") + where; 367 | sql += limit; 368 | NOrmQuery query(db); 369 | query.prepare(sql); 370 | resolvedWhere.bindValues(query); 371 | return query; 372 | } 373 | 374 | /** Returns the SQL query to perform a DELETE on the current set. 375 | */ 376 | NOrmQuery NOrmQuerySetPrivate::deleteQuery() const { 377 | QSqlDatabase db = NOrm::database(); 378 | 379 | // build query 380 | NOrmCompiler compiler(m_modelName, db); 381 | NOrmWhere resolvedWhere(whereClause); 382 | compiler.resolve(resolvedWhere); 383 | 384 | const QString where = resolvedWhere.sql(db); 385 | const QString limit = compiler.orderLimitSql(orderBy, lowMark, highMark); 386 | QString sql = QLatin1String("DELETE FROM ") + compiler.fromSql(); 387 | if (!where.isEmpty()) 388 | sql += QLatin1String(" WHERE ") + where; 389 | sql += limit; 390 | NOrmQuery query(db); 391 | query.prepare(sql); 392 | resolvedWhere.bindValues(query); 393 | 394 | return query; 395 | } 396 | 397 | /** Returns the SQL query to perform an INSERT for the specified \a fields. 398 | */ 399 | NOrmQuery NOrmQuerySetPrivate::insertQuery(const QVariantMap& fields) const { 400 | QSqlDatabase db = NOrm::database(); 401 | const NOrmMetaModel metaModel = NOrm::metaModel(m_modelName); 402 | 403 | // perform INSERT 404 | QStringList fieldColumns; 405 | QStringList fieldHolders; 406 | foreach (const QString& name, fields.keys()) { 407 | const NOrmMetaField field = metaModel.localField(name.toLatin1()); 408 | fieldColumns << db.driver()->escapeIdentifier(field.column(), QSqlDriver::FieldName); 409 | fieldHolders << QLatin1String("?"); 410 | } 411 | 412 | NOrmQuery query(db); 413 | query.prepare(QString::fromLatin1("INSERT INTO %1 (%2) VALUES(%3)") 414 | .arg(db.driver()->escapeIdentifier(metaModel.table(), QSqlDriver::TableName), 415 | fieldColumns.join(QLatin1String(", ")), fieldHolders.join(QLatin1String(", ")))); 416 | foreach (const QString& name, fields.keys()) 417 | query.addBindValue(fields.value(name)); 418 | return query; 419 | } 420 | 421 | /** Returns the SQL query to perform a SELECT on the current set. 422 | */ 423 | NOrmQuery NOrmQuerySetPrivate::selectQuery() const { 424 | QSqlDatabase db = NOrm::database(); 425 | 426 | // build query 427 | NOrmCompiler compiler(m_modelName, db); 428 | NOrmWhere resolvedWhere(whereClause); 429 | compiler.resolve(resolvedWhere); 430 | 431 | const QStringList columns = compiler.fieldNames(selectRelated, &this->relatedFields); 432 | const QString where = resolvedWhere.sql(db); 433 | const QString limit = compiler.orderLimitSql(orderBy, lowMark, highMark); 434 | QString sql = QLatin1String("SELECT ") + columns.join(QLatin1String(", ")) + QLatin1String(" FROM ") + compiler.fromSql(); 435 | if (!where.isEmpty()) 436 | sql += QLatin1String(" WHERE ") + where; 437 | sql += limit; 438 | NOrmQuery query(db); 439 | query.prepare(sql); 440 | resolvedWhere.bindValues(query); 441 | 442 | return query; 443 | } 444 | 445 | /** Returns the SQL query to perform an UPDATE on the current set for the 446 | specified \a fields. 447 | */ 448 | NOrmQuery NOrmQuerySetPrivate::updateQuery(const QVariantMap& fields) const { 449 | QSqlDatabase db = NOrm::database(); 450 | const NOrmMetaModel metaModel = NOrm::metaModel(m_modelName); 451 | 452 | // build query 453 | NOrmCompiler compiler(m_modelName, db); 454 | NOrmWhere resolvedWhere(whereClause); 455 | compiler.resolve(resolvedWhere); 456 | 457 | QString sql = QLatin1String("UPDATE ") + compiler.fromSql(); 458 | 459 | // add SET 460 | QStringList fieldAssign; 461 | foreach (const QString& name, fields.keys()) { 462 | const NOrmMetaField field = metaModel.localField(name.toLatin1()); 463 | fieldAssign << db.driver()->escapeIdentifier(field.column(), QSqlDriver::FieldName) + QLatin1String(" = ?"); 464 | } 465 | sql += QLatin1String(" SET ") + fieldAssign.join(QLatin1String(", ")); 466 | 467 | // add WHERE 468 | const QString where = resolvedWhere.sql(db); 469 | if (!where.isEmpty()) 470 | sql += QLatin1String(" WHERE ") + where; 471 | 472 | NOrmQuery query(db); 473 | query.prepare(sql); 474 | foreach (const QString& name, fields.keys()) 475 | query.addBindValue(fields.value(name)); 476 | resolvedWhere.bindValues(query); 477 | 478 | return query; 479 | } 480 | 481 | int NOrmQuerySetPrivate::sqlUpdate(const QVariantMap& fields) { 482 | // UPDATE on an empty queryset doesn't need a query 483 | if (whereClause.isNone() || fields.isEmpty()) 484 | return 0; 485 | 486 | // FIXME : it is not possible to update entries once a limit has been set 487 | // because SQLite does not support limits on UPDATE unless compiled with the 488 | // SQLITE_ENABLE_UPDATE_DELETE_LIMIT option 489 | if (lowMark || highMark) 490 | return -1; 491 | 492 | // execute query 493 | NOrmQuery query(updateQuery(fields)); 494 | if (!query.exec()) 495 | return -1; 496 | 497 | // invalidate cache 498 | if (hasResults) { 499 | properties.clear(); 500 | hasResults = false; 501 | } 502 | 503 | return query.numRowsAffected(); 504 | } 505 | 506 | QList NOrmQuerySetPrivate::sqlValues(const QStringList& fields) { 507 | QList values; 508 | if (!sqlFetch()) 509 | return values; 510 | 511 | const NOrmMetaModel metaModel = NOrm::metaModel(m_modelName); 512 | 513 | // build field list 514 | const QList localFields = metaModel.localFields(); 515 | QMap fieldPos; 516 | if (fields.isEmpty()) { 517 | for (int i = 0; i < localFields.size(); ++i) 518 | fieldPos.insert(localFields[i].name(), i); 519 | } else { 520 | foreach (const QString& name, fields) { 521 | int pos = 0; 522 | foreach (const NOrmMetaField& field, localFields) { 523 | if (field.name() == name) 524 | break; 525 | pos++; 526 | } 527 | Q_ASSERT_X(pos < localFields.size(), "NOrmQuerySet::values", "unknown field requested"); 528 | fieldPos.insert(name, pos); 529 | } 530 | } 531 | 532 | // extract values 533 | foreach (const QVariantList& props, properties) { 534 | QVariantMap map; 535 | QMap::const_iterator i; 536 | for (i = fieldPos.constBegin(); i != fieldPos.constEnd(); ++i) 537 | map[i.key()] = props[i.value()]; 538 | values.append(map); 539 | } 540 | return values; 541 | } 542 | 543 | QList NOrmQuerySetPrivate::sqlValuesList(const QStringList& fields) { 544 | QList values; 545 | if (!sqlFetch()) 546 | return values; 547 | 548 | const NOrmMetaModel metaModel = NOrm::metaModel(m_modelName); 549 | 550 | // build field list 551 | const QList localFields = metaModel.localFields(); 552 | QList fieldPos; 553 | if (fields.isEmpty()) { 554 | for (int i = 0; i < localFields.size(); ++i) 555 | fieldPos << i; 556 | } else { 557 | foreach (const QString& name, fields) { 558 | int pos = 0; 559 | foreach (const NOrmMetaField& field, localFields) { 560 | if (field.name() == name) 561 | break; 562 | pos++; 563 | } 564 | Q_ASSERT_X(pos < localFields.size(), "NOrmQuerySet::valuesList", "unknown field requested"); 565 | fieldPos << pos; 566 | } 567 | } 568 | 569 | // extract values 570 | foreach (const QVariantList& props, properties) { 571 | QVariantList list; 572 | foreach (int pos, fieldPos) 573 | list << props.at(pos); 574 | values.append(list); 575 | } 576 | return values; 577 | } 578 | 579 | /// \endcond 580 | -------------------------------------------------------------------------------- /NORM/src/NOrmWhere.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "NOrm.h" 5 | #include "NOrmWhere.h" 6 | #include "NOrmWhere_p.h" 7 | 8 | static QString escapeLike(const QString &data) 9 | { 10 | QString escaped = data; 11 | escaped.replace(QLatin1String("%"), QLatin1String("\\%")); 12 | escaped.replace(QLatin1String("_"), QLatin1String("\\_")); 13 | return escaped; 14 | } 15 | 16 | /// \cond 17 | 18 | NOrmWherePrivate::NOrmWherePrivate() 19 | : operation(NOrmWhere::None) 20 | , combine(NoCombine) 21 | , negate(false) 22 | { 23 | } 24 | 25 | NOrmWhere::NOrmWhere() 26 | { 27 | d = new NOrmWherePrivate; 28 | } 29 | 30 | NOrmWhere::NOrmWhere(const NOrmWhere &other) 31 | : d(other.d) 32 | { 33 | } 34 | 35 | NOrmWhere::NOrmWhere(const QString &key, NOrmWhere::Operation operation, QVariant value) 36 | { 37 | d = new NOrmWherePrivate; 38 | d->key = key; 39 | d->operation = operation; 40 | d->data = value; 41 | } 42 | 43 | NOrmWhere::~NOrmWhere() 44 | { 45 | } 46 | 47 | NOrmWhere& NOrmWhere::operator=(const NOrmWhere& other) 48 | { 49 | d = other.d; 50 | return *this; 51 | } 52 | 53 | NOrmWhere NOrmWhere::operator!() const 54 | { 55 | NOrmWhere result; 56 | result.d = d; 57 | if (d->children.isEmpty()) { 58 | switch (d->operation) 59 | { 60 | case None: 61 | case IsIn: 62 | case StartsWith: 63 | case IStartsWith: 64 | case EndsWith: 65 | case IEndsWith: 66 | case Contains: 67 | case IContains: 68 | result.d->negate = !d->negate; 69 | break; 70 | case IsNull: 71 | // simplify !(is null) to is not null 72 | result.d->data = !d->data.toBool(); 73 | break; 74 | case Equals: 75 | // simplify !(a = b) to a != b 76 | result.d->operation = NotEquals; 77 | break; 78 | case IEquals: 79 | // simplify !(a = b) to a != b 80 | result.d->operation = INotEquals; 81 | break; 82 | case NotEquals: 83 | // simplify !(a != b) to a = b 84 | result.d->operation = Equals; 85 | break; 86 | case INotEquals: 87 | // simplify !(a != b) to a = b 88 | result.d->operation = IEquals; 89 | break; 90 | case GreaterThan: 91 | // simplify !(a > b) to a <= b 92 | result.d->operation = LessOrEquals; 93 | break; 94 | case LessThan: 95 | // simplify !(a < b) to a >= b 96 | result.d->operation = GreaterOrEquals; 97 | break; 98 | case GreaterOrEquals: 99 | // simplify !(a >= b) to a < b 100 | result.d->operation = LessThan; 101 | break; 102 | case LessOrEquals: 103 | // simplify !(a <= b) to a > b 104 | result.d->operation = GreaterThan; 105 | break; 106 | } 107 | } else { 108 | result.d->negate = !d->negate; 109 | } 110 | 111 | return result; 112 | } 113 | 114 | NOrmWhere NOrmWhere::operator&&(const NOrmWhere &other) const 115 | { 116 | if (isAll() || other.isNone()) 117 | return other; 118 | else if (isNone() || other.isAll()) 119 | return *this; 120 | 121 | if (d->combine == NOrmWherePrivate::AndCombine) { 122 | NOrmWhere result = *this; 123 | result.d->children << other; 124 | return result; 125 | } 126 | 127 | NOrmWhere result; 128 | result.d->combine = NOrmWherePrivate::AndCombine; 129 | result.d->children << *this << other; 130 | return result; 131 | } 132 | 133 | NOrmWhere NOrmWhere::operator||(const NOrmWhere &other) const 134 | { 135 | if (isAll() || other.isNone()) 136 | return *this; 137 | else if (isNone() || other.isAll()) 138 | return other; 139 | 140 | if (d->combine == NOrmWherePrivate::OrCombine) { 141 | NOrmWhere result = *this; 142 | result.d->children << other; 143 | return result; 144 | } 145 | 146 | NOrmWhere result; 147 | result.d->combine = NOrmWherePrivate::OrCombine; 148 | result.d->children << *this << other; 149 | return result; 150 | } 151 | 152 | void NOrmWhere::bindValues(NOrmQuery &query) const 153 | { 154 | if (d->operation == NOrmWhere::IsIn) { 155 | const QList values = d->data.toList(); 156 | for (int i = 0; i < values.size(); i++) 157 | query.addBindValue(values[i]); 158 | } else if (d->operation == NOrmWhere::IsNull) { 159 | // no data to bind 160 | } else if (d->operation == NOrmWhere::StartsWith || d->operation == NOrmWhere::IStartsWith) { 161 | query.addBindValue(escapeLike(d->data.toString()) + QLatin1String("%")); 162 | } else if (d->operation == NOrmWhere::EndsWith || d->operation == NOrmWhere::IEndsWith) { 163 | query.addBindValue(QLatin1String("%") + escapeLike(d->data.toString())); 164 | } else if (d->operation == NOrmWhere::Contains || d->operation == NOrmWhere::IContains) { 165 | query.addBindValue(QLatin1String("%") + escapeLike(d->data.toString()) + QLatin1String("%")); 166 | } else if (d->operation != NOrmWhere::None) { 167 | query.addBindValue(d->data); 168 | } else { 169 | foreach (const NOrmWhere &child, d->children) 170 | child.bindValues(query); 171 | } 172 | } 173 | 174 | bool NOrmWhere::isAll() const 175 | { 176 | return d->combine == NOrmWherePrivate::NoCombine && d->operation == None && d->negate == false; 177 | } 178 | 179 | bool NOrmWhere::isNone() const 180 | { 181 | return d->combine == NOrmWherePrivate::NoCombine && d->operation == None && d->negate == true; 182 | } 183 | 184 | QString NOrmWhere::sql(const QSqlDatabase &db) const 185 | { 186 | NOrmDatabase::DatabaseType databaseType = NOrmDatabase::databaseType(db); 187 | 188 | switch (databaseType) { 189 | case NOrmDatabase::UnknownDB: 190 | case NOrmDatabase::Oracle: 191 | case NOrmDatabase::Sybase: 192 | case NOrmDatabase::Interbase: 193 | case NOrmDatabase::DB2: 194 | case NOrmDatabase::MSSqlServer: 195 | return sqlDefult(db); 196 | case NOrmDatabase::MySqlServer: 197 | return sqlMysql(db); 198 | case NOrmDatabase::PostgreSQL: 199 | return sqlPostgre(db); 200 | case NOrmDatabase::SQLite: 201 | return sqlSqlite(db); 202 | case NOrmDatabase::DaMeng: 203 | return sqlDaMeng(db); 204 | } 205 | return QString(); 206 | } 207 | 208 | QString NOrmWhere::toString() const 209 | { 210 | if (d->combine == NOrmWherePrivate::NoCombine) { 211 | return QLatin1String("NOrmWhere(") 212 | + "key=\"" + d->key + "\"" 213 | + ", operation=\"" + NOrmWherePrivate::operationToString(d->operation) + "\"" 214 | + ", value=\"" + d->data.toString() + "\"" 215 | + ", negate=" + (d->negate ? "true":"false") 216 | + ")"; 217 | } else { 218 | QStringList bits; 219 | foreach (const NOrmWhere &childWhere, d->children) { 220 | bits.append(childWhere.toString()); 221 | } 222 | if (d->combine == NOrmWherePrivate::OrCombine) { 223 | return bits.join(" || "); 224 | } else { 225 | return bits.join(" && "); 226 | } 227 | } 228 | } 229 | 230 | QString NOrmWhere::sqlDefult(const QSqlDatabase &db) const 231 | { 232 | switch (d->operation) { 233 | case Equals: 234 | return d->key + QLatin1String(" = ?"); 235 | case NotEquals: 236 | return d->key + QLatin1String(" != ?"); 237 | case GreaterThan: 238 | return d->key + QLatin1String(" > ?"); 239 | case LessThan: 240 | return d->key + QLatin1String(" < ?"); 241 | case GreaterOrEquals: 242 | return d->key + QLatin1String(" >= ?"); 243 | case LessOrEquals: 244 | return d->key + QLatin1String(" <= ?"); 245 | case IsIn: 246 | { 247 | QStringList bits; 248 | for (int i = 0; i < d->data.toList().size(); i++) 249 | bits << QLatin1String("?"); 250 | if (d->negate) 251 | return d->key + QString::fromLatin1(" NOT IN (%1)").arg(bits.join(QLatin1String(", "))); 252 | else 253 | return d->key + QString::fromLatin1(" IN (%1)").arg(bits.join(QLatin1String(", "))); 254 | } 255 | case IsNull: 256 | return d->key + QLatin1String(d->data.toBool() ? " IS NULL" : " IS NOT NULL"); 257 | case StartsWith: 258 | case EndsWith: 259 | case Contains: 260 | { 261 | QString op; 262 | op = QLatin1String(d->negate ? "NOT LIKE" : "LIKE"); 263 | return d->key + QLatin1String(" ") + op + QLatin1String(" ?"); 264 | } 265 | case IStartsWith: 266 | case IEndsWith: 267 | case IContains: 268 | case IEquals: 269 | { 270 | const QString op = QLatin1String(d->negate ? "NOT LIKE" : "LIKE"); 271 | return d->key + QLatin1String(" ") + op + QLatin1String(" ?"); 272 | } 273 | case INotEquals: 274 | { 275 | const QString op = QLatin1String(d->negate ? "LIKE" : "NOT LIKE"); 276 | return d->key + QLatin1String(" ") + op + QLatin1String(" ?"); 277 | } 278 | case None: 279 | if (d->combine == NOrmWherePrivate::NoCombine) { 280 | return d->negate ? QLatin1String("1 != 0") : QString(); 281 | } else { 282 | QStringList bits; 283 | foreach (const NOrmWhere &child, d->children) { 284 | QString atom = child.sql(db); 285 | if (child.d->children.isEmpty()) 286 | bits << atom; 287 | else 288 | bits << QString::fromLatin1("(%1)").arg(atom); 289 | } 290 | 291 | QString combined; 292 | if (d->combine == NOrmWherePrivate::AndCombine) 293 | combined = bits.join(QLatin1String(" AND ")); 294 | else if (d->combine == NOrmWherePrivate::OrCombine) 295 | combined = bits.join(QLatin1String(" OR ")); 296 | if (d->negate) 297 | combined = QString::fromLatin1("NOT (%1)").arg(combined); 298 | return combined; 299 | } 300 | } 301 | 302 | return QString(); 303 | } 304 | 305 | QString NOrmWhere::sqlMysql(const QSqlDatabase &db) const 306 | { 307 | switch (d->operation) { 308 | case Equals: 309 | return d->key + QLatin1String(" = ?"); 310 | case NotEquals: 311 | return d->key + QLatin1String(" != ?"); 312 | case GreaterThan: 313 | return d->key + QLatin1String(" > ?"); 314 | case LessThan: 315 | return d->key + QLatin1String(" < ?"); 316 | case GreaterOrEquals: 317 | return d->key + QLatin1String(" >= ?"); 318 | case LessOrEquals: 319 | return d->key + QLatin1String(" <= ?"); 320 | case IsIn: 321 | { 322 | QStringList bits; 323 | for (int i = 0; i < d->data.toList().size(); i++) 324 | bits << QLatin1String("?"); 325 | if (d->negate) 326 | return d->key + QString::fromLatin1(" NOT IN (%1)").arg(bits.join(QLatin1String(", "))); 327 | else 328 | return d->key + QString::fromLatin1(" IN (%1)").arg(bits.join(QLatin1String(", "))); 329 | } 330 | case IsNull: 331 | return d->key + QLatin1String(d->data.toBool() ? " IS NULL" : " IS NOT NULL"); 332 | case StartsWith: 333 | case EndsWith: 334 | case Contains: 335 | { 336 | QString op; 337 | op = QLatin1String(d->negate ? "NOT LIKE BINARY" : "LIKE BINARY"); 338 | return d->key + QLatin1String(" ") + op + QLatin1String(" ?"); 339 | } 340 | case IStartsWith: 341 | case IEndsWith: 342 | case IContains: 343 | case IEquals: 344 | { 345 | const QString op = QLatin1String(d->negate ? "NOT LIKE" : "LIKE"); 346 | return d->key + QLatin1String(" ") + op + QLatin1String(" ?"); 347 | } 348 | case INotEquals: 349 | { 350 | const QString op = QLatin1String(d->negate ? "LIKE" : "NOT LIKE"); 351 | return d->key + QLatin1String(" ") + op + QLatin1String(" ?"); 352 | } 353 | case None: 354 | if (d->combine == NOrmWherePrivate::NoCombine) { 355 | return d->negate ? QLatin1String("1 != 0") : QString(); 356 | } else { 357 | QStringList bits; 358 | foreach (const NOrmWhere &child, d->children) { 359 | QString atom = child.sql(db); 360 | if (child.d->children.isEmpty()) 361 | bits << atom; 362 | else 363 | bits << QString::fromLatin1("(%1)").arg(atom); 364 | } 365 | 366 | QString combined; 367 | if (d->combine == NOrmWherePrivate::AndCombine) 368 | combined = bits.join(QLatin1String(" AND ")); 369 | else if (d->combine == NOrmWherePrivate::OrCombine) 370 | combined = bits.join(QLatin1String(" OR ")); 371 | if (d->negate) 372 | combined = QString::fromLatin1("NOT (%1)").arg(combined); 373 | return combined; 374 | } 375 | } 376 | 377 | return QString(); 378 | } 379 | 380 | QString NOrmWhere::sqlSqlite(const QSqlDatabase &db) const 381 | { 382 | switch (d->operation) { 383 | case Equals: 384 | return d->key + QLatin1String(" = ?"); 385 | case NotEquals: 386 | return d->key + QLatin1String(" != ?"); 387 | case GreaterThan: 388 | return d->key + QLatin1String(" > ?"); 389 | case LessThan: 390 | return d->key + QLatin1String(" < ?"); 391 | case GreaterOrEquals: 392 | return d->key + QLatin1String(" >= ?"); 393 | case LessOrEquals: 394 | return d->key + QLatin1String(" <= ?"); 395 | case IsIn: 396 | { 397 | QStringList bits; 398 | for (int i = 0; i < d->data.toList().size(); i++) 399 | bits << QLatin1String("?"); 400 | if (d->negate) 401 | return d->key + QString::fromLatin1(" NOT IN (%1)").arg(bits.join(QLatin1String(", "))); 402 | else 403 | return d->key + QString::fromLatin1(" IN (%1)").arg(bits.join(QLatin1String(", "))); 404 | } 405 | case IsNull: 406 | return d->key + QLatin1String(d->data.toBool() ? " IS NULL" : " IS NOT NULL"); 407 | case StartsWith: 408 | case EndsWith: 409 | case Contains: 410 | { 411 | QString op; 412 | op = QLatin1String(d->negate ? "NOT LIKE" : "LIKE"); 413 | return d->key + QLatin1String(" ") + op + QLatin1String(" ? ESCAPE '\\'"); 414 | } 415 | case IStartsWith: 416 | case IEndsWith: 417 | case IContains: 418 | case IEquals: 419 | { 420 | const QString op = QLatin1String(d->negate ? "NOT LIKE" : "LIKE"); 421 | return d->key + QLatin1String(" ") + op + QLatin1String(" ? ESCAPE '\\'"); 422 | } 423 | case INotEquals: 424 | { 425 | const QString op = QLatin1String(d->negate ? "LIKE" : "NOT LIKE"); 426 | return d->key + QLatin1String(" ") + op + QLatin1String(" ? ESCAPE '\\'"); 427 | } 428 | case None: 429 | if (d->combine == NOrmWherePrivate::NoCombine) { 430 | return d->negate ? QLatin1String("1 != 0") : QString(); 431 | } else { 432 | QStringList bits; 433 | foreach (const NOrmWhere &child, d->children) { 434 | QString atom = child.sql(db); 435 | if (child.d->children.isEmpty()) 436 | bits << atom; 437 | else 438 | bits << QString::fromLatin1("(%1)").arg(atom); 439 | } 440 | 441 | QString combined; 442 | if (d->combine == NOrmWherePrivate::AndCombine) 443 | combined = bits.join(QLatin1String(" AND ")); 444 | else if (d->combine == NOrmWherePrivate::OrCombine) 445 | combined = bits.join(QLatin1String(" OR ")); 446 | if (d->negate) 447 | combined = QString::fromLatin1("NOT (%1)").arg(combined); 448 | return combined; 449 | } 450 | } 451 | 452 | return QString(); 453 | } 454 | 455 | QString NOrmWhere::sqlPostgre(const QSqlDatabase &db) const 456 | { 457 | switch (d->operation) { 458 | case Equals: 459 | return d->key + QLatin1String(" = ?"); 460 | case NotEquals: 461 | return d->key + QLatin1String(" != ?"); 462 | case GreaterThan: 463 | return d->key + QLatin1String(" > ?"); 464 | case LessThan: 465 | return d->key + QLatin1String(" < ?"); 466 | case GreaterOrEquals: 467 | return d->key + QLatin1String(" >= ?"); 468 | case LessOrEquals: 469 | return d->key + QLatin1String(" <= ?"); 470 | case IsIn: 471 | { 472 | QStringList bits; 473 | for (int i = 0; i < d->data.toList().size(); i++) 474 | bits << QLatin1String("?"); 475 | if (d->negate) 476 | return d->key + QString::fromLatin1(" NOT IN (%1)").arg(bits.join(QLatin1String(", "))); 477 | else 478 | return d->key + QString::fromLatin1(" IN (%1)").arg(bits.join(QLatin1String(", "))); 479 | } 480 | case IsNull: 481 | return d->key + QLatin1String(d->data.toBool() ? " IS NULL" : " IS NOT NULL"); 482 | case StartsWith: 483 | case EndsWith: 484 | case Contains: 485 | { 486 | QString op; 487 | op = QLatin1String(d->negate ? "NOT LIKE" : "LIKE"); 488 | return d->key + QLatin1String(" ") + op + QLatin1String(" ?"); 489 | } 490 | case IStartsWith: 491 | case IEndsWith: 492 | case IContains: 493 | case IEquals: 494 | { 495 | const QString op = QLatin1String(d->negate ? "NOT LIKE" : "LIKE"); 496 | return QLatin1String("UPPER(") + d->key + QLatin1String("::text) ") + op + QLatin1String(" UPPER(?)"); 497 | } 498 | case INotEquals: 499 | { 500 | const QString op = QLatin1String(d->negate ? "LIKE" : "NOT LIKE"); 501 | return QLatin1String("UPPER(") + d->key + QLatin1String("::text) ") + op + QLatin1String(" UPPER(?)"); 502 | } 503 | case None: 504 | if (d->combine == NOrmWherePrivate::NoCombine) { 505 | return d->negate ? QLatin1String("1 != 0") : QString(); 506 | } else { 507 | QStringList bits; 508 | foreach (const NOrmWhere &child, d->children) { 509 | QString atom = child.sql(db); 510 | if (child.d->children.isEmpty()) 511 | bits << atom; 512 | else 513 | bits << QString::fromLatin1("(%1)").arg(atom); 514 | } 515 | 516 | QString combined; 517 | if (d->combine == NOrmWherePrivate::AndCombine) 518 | combined = bits.join(QLatin1String(" AND ")); 519 | else if (d->combine == NOrmWherePrivate::OrCombine) 520 | combined = bits.join(QLatin1String(" OR ")); 521 | if (d->negate) 522 | combined = QString::fromLatin1("NOT (%1)").arg(combined); 523 | return combined; 524 | } 525 | } 526 | 527 | return QString(); 528 | } 529 | 530 | QString NOrmWhere::sqlDaMeng(const QSqlDatabase &db) const 531 | { 532 | QString tmp; 533 | if (d.data()->data.type() == QVariant::String) { 534 | tmp = "?"; 535 | } else { 536 | tmp = "?"; 537 | } 538 | 539 | switch (d->operation) { 540 | case Equals: 541 | return d->key + QString(" = %1").arg(tmp); 542 | case NotEquals: 543 | return d->key + QString(" != %1").arg(tmp); 544 | case GreaterThan: 545 | return d->key + QString(" > %1").arg(tmp); 546 | case LessThan: 547 | return d->key + QString(" < %1").arg(tmp); 548 | case GreaterOrEquals: 549 | return d->key + QString(" >= %1").arg(tmp); 550 | case LessOrEquals: 551 | return d->key + QString(" <= %1").arg(tmp); 552 | case IsIn: 553 | { 554 | QStringList bits; 555 | for (int i = 0; i < d->data.toList().size(); i++) 556 | bits << QString("%1").arg(tmp); 557 | if (d->negate) 558 | return d->key + QString::fromLatin1(" NOT IN (%1)").arg(bits.join(QLatin1String(", "))); 559 | else 560 | return d->key + QString::fromLatin1(" IN (%1)").arg(bits.join(QLatin1String(", "))); 561 | } 562 | case IsNull: 563 | return d->key + QLatin1String(d->data.toBool() ? " IS NULL" : " IS NOT NULL"); 564 | case StartsWith: 565 | case EndsWith: 566 | case Contains: 567 | { 568 | QString op; 569 | op = QLatin1String(d->negate ? "NOT LIKE" : "LIKE"); 570 | return d->key + QLatin1String(" ") + op + QString(" %1").arg(tmp); 571 | } 572 | case IStartsWith: 573 | case IEndsWith: 574 | case IContains: 575 | case IEquals: 576 | { 577 | const QString op = QLatin1String(d->negate ? "NOT LIKE" : "LIKE"); 578 | return d->key + QLatin1String(" ") + op + QString(" %1").arg(tmp); 579 | } 580 | case INotEquals: 581 | { 582 | const QString op = QLatin1String(d->negate ? "LIKE" : "NOT LIKE"); 583 | return d->key + QLatin1String(" ") + op + QString(" %1").arg(tmp); 584 | } 585 | case None: 586 | if (d->combine == NOrmWherePrivate::NoCombine) { 587 | return d->negate ? QLatin1String("1 != 0") : QString(); 588 | } else { 589 | QStringList bits; 590 | foreach (const NOrmWhere &child, d->children) { 591 | QString atom = child.sql(db); 592 | if (child.d->children.isEmpty()) 593 | bits << atom; 594 | else 595 | bits << QString::fromLatin1("(%1)").arg(atom); 596 | } 597 | 598 | QString combined; 599 | if (d->combine == NOrmWherePrivate::AndCombine) 600 | combined = bits.join(QLatin1String(" AND ")); 601 | else if (d->combine == NOrmWherePrivate::OrCombine) 602 | combined = bits.join(QLatin1String(" OR ")); 603 | if (d->negate) 604 | combined = QString::fromLatin1("NOT (%1)").arg(combined); 605 | return combined; 606 | } 607 | } 608 | 609 | return QString(); 610 | } 611 | QString NOrmWherePrivate::operationToString(NOrmWhere::Operation operation) 612 | { 613 | switch (operation) { 614 | case NOrmWhere::Equals: return QLatin1String("Equals"); 615 | case NOrmWhere::IEquals: return QLatin1String("IEquals"); 616 | case NOrmWhere::NotEquals: return QLatin1String("NotEquals"); 617 | case NOrmWhere::INotEquals: return QLatin1String("INotEquals"); 618 | case NOrmWhere::GreaterThan: return QLatin1String("GreaterThan"); 619 | case NOrmWhere::LessThan: return QLatin1String("LessThan"); 620 | case NOrmWhere::GreaterOrEquals: return QLatin1String("GreaterOrEquals"); 621 | case NOrmWhere::LessOrEquals: return QLatin1String("LessOrEquals"); 622 | case NOrmWhere::StartsWith: return QLatin1String("StartsWith"); 623 | case NOrmWhere::IStartsWith: return QLatin1String("IStartsWith"); 624 | case NOrmWhere::EndsWith: return QLatin1String("EndsWith"); 625 | case NOrmWhere::IEndsWith: return QLatin1String("IEndsWith"); 626 | case NOrmWhere::Contains: return QLatin1String("Contains"); 627 | case NOrmWhere::IContains: return QLatin1String("IContains"); 628 | case NOrmWhere::IsIn: return QLatin1String("IsIn"); 629 | case NOrmWhere::IsNull: return QLatin1String("IsNull"); 630 | case NOrmWhere::None: 631 | default: 632 | return QLatin1String(""); 633 | } 634 | 635 | return QString(); 636 | } 637 | -------------------------------------------------------------------------------- /NORM/src/NOrmMetaModel.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include "NOrm.h" 6 | #include "NOrmMetaModel.h" 7 | #include "NOrmQuerySet_p.h" 8 | 9 | // python-compatible hash 10 | static long string_hash(const QString &s) 11 | { 12 | if (s.isEmpty()) 13 | return 0; 14 | 15 | const QByteArray a = s.toLatin1(); 16 | unsigned char *p = (unsigned char *) a.constData(); 17 | long x = *p << 7; 18 | for (int i = 0; i < a.size(); ++i) 19 | x = (1000003*x) ^ *p++; 20 | x ^= a.size(); 21 | return (x == -1) ? -2 : x; 22 | } 23 | 24 | static long stringlist_hash(const QStringList &l) 25 | { 26 | long x = 0x345678L; 27 | long mult = 1000003L; 28 | int len = l.size(); 29 | foreach (const QString &s, l) { 30 | --len; 31 | x = (x ^ string_hash(s)) * mult; 32 | mult += (long)(82520L + len + len); 33 | } 34 | x += 97531L; 35 | return (x == -1) ? -2 : x; 36 | } 37 | 38 | static QString stringlist_digest(const QStringList &l) 39 | { 40 | return QString::number(labs(stringlist_hash(l)) % 4294967296UL, 16); 41 | } 42 | 43 | enum ForeignKeyConstraint 44 | { 45 | NoAction, 46 | Restrict, 47 | Cascade, 48 | SetNull 49 | }; 50 | 51 | // 数据表对象字段私有类 52 | class NOrmMetaFieldPrivate : public QSharedData 53 | { 54 | public: 55 | NOrmMetaFieldPrivate(); 56 | 57 | // 是否自增索引 58 | bool autoIncrement; 59 | 60 | // 列名字 61 | QString db_column; 62 | 63 | // 外键管理模型名字 64 | QByteArray foreignModel; 65 | 66 | // 是否是索引字段 67 | bool index; 68 | 69 | // 最大长度(字符串类型) 70 | int maxLength; 71 | 72 | // 模型列名字 73 | QByteArray name; 74 | 75 | // 是否允许null 76 | bool null; 77 | 78 | // 数据字段类型 79 | QVariant::Type type; 80 | 81 | // 是否是唯一性约束 82 | bool unique; 83 | 84 | // 是否是空 85 | bool blank; 86 | 87 | // 是否含有删除约束 88 | ForeignKeyConstraint deleteConstraint; 89 | }; 90 | 91 | NOrmMetaFieldPrivate::NOrmMetaFieldPrivate() 92 | : autoIncrement(false) 93 | , index(false) 94 | , maxLength(0) 95 | , null(false) 96 | , unique(false) 97 | , blank(false) 98 | , deleteConstraint(NoAction) 99 | { 100 | } 101 | 102 | NOrmMetaField::NOrmMetaField() 103 | { 104 | d = new NOrmMetaFieldPrivate; 105 | } 106 | 107 | NOrmMetaField::NOrmMetaField(const NOrmMetaField &other) 108 | : d(other.d) 109 | { 110 | } 111 | 112 | NOrmMetaField::~NOrmMetaField() 113 | { 114 | } 115 | 116 | NOrmMetaField& NOrmMetaField::operator=(const NOrmMetaField& other) 117 | { 118 | d = other.d; 119 | return *this; 120 | } 121 | 122 | QString NOrmMetaField::column() const 123 | { 124 | return d->db_column; 125 | } 126 | 127 | bool NOrmMetaField::isNullable() const 128 | { 129 | return d->null; 130 | } 131 | 132 | bool NOrmMetaField::isValid() const 133 | { 134 | return !d->name.isEmpty(); 135 | } 136 | 137 | bool NOrmMetaField::isAutoIncrement() const 138 | { 139 | return d->autoIncrement; 140 | } 141 | 142 | bool NOrmMetaField::isUnique() const 143 | { 144 | return d->unique; 145 | } 146 | 147 | bool NOrmMetaField::isBlank() const 148 | { 149 | return d->blank; 150 | } 151 | 152 | QString NOrmMetaField::name() const 153 | { 154 | return QString::fromLatin1(d->name); 155 | } 156 | 157 | int NOrmMetaField::maxLength() const 158 | { 159 | return d->maxLength; 160 | } 161 | 162 | QVariant NOrmMetaField::toDatabase(const QVariant &value) const 163 | { 164 | if (d->type == QVariant::String && !d->null && value.isNull()){ 165 | return QLatin1String(""); 166 | } else if (!d->foreignModel.isEmpty() && d->type == QVariant::Int && d->null && !value.toInt()) { 167 | return QVariant(); 168 | } else if (d->type == QVariant::StringList) { 169 | QStringList tmpStr = value.value(); 170 | QString arrary_data; 171 | foreach (QString item, tmpStr) { 172 | arrary_data += QObject::tr("%1,").arg(item); 173 | } 174 | arrary_data = arrary_data.left(arrary_data.length()-1); 175 | if(arrary_data.isNull()){ 176 | arrary_data = ""; 177 | } 178 | QVariant tmpValue(arrary_data); 179 | return tmpValue; 180 | } else { 181 | return value; 182 | } 183 | } 184 | 185 | static QMap parseOptions(const char *value) 186 | { 187 | QMap options; 188 | QStringList items = QString::fromLatin1(value).split(QLatin1Char(' '), QString::SkipEmptyParts); 189 | foreach (const QString &item, items) { 190 | QStringList assign = item.split(QLatin1Char('=')); 191 | if (assign.size() == 2) { 192 | options[assign[0].toLower()] = assign[1]; 193 | } else { 194 | qWarning() << "Could not parse option" << item; 195 | } 196 | } 197 | return options; 198 | } 199 | 200 | static bool stringToBool(const QString &value) 201 | { 202 | return value.toLower() == QLatin1String("true") || value == QLatin1String("1"); 203 | } 204 | 205 | // 数据表元模型似有类 206 | class NOrmMetaModelPrivate : public QSharedData 207 | { 208 | public: 209 | // 数据表名字 210 | QString className; 211 | 212 | // 包含的列信息 213 | QList localFields; 214 | 215 | // 外键列信息 216 | QMap foreignFields; 217 | 218 | // 主键列表 219 | QByteArray primaryKey; 220 | 221 | // 表名字 222 | QString table; 223 | 224 | // 唯一性字段 225 | QList uniqueTogether; 226 | }; 227 | 228 | NOrmMetaModel::NOrmMetaModel(const QMetaObject *meta) : d(new NOrmMetaModelPrivate) 229 | { 230 | if (!meta) 231 | return; 232 | 233 | // 类名字映射 234 | d->className = meta->className(); 235 | 236 | // 表名字映射 237 | d->table = QString::fromLatin1(meta->className()).toLower(); 238 | 239 | // 解析数据表的操作项 240 | const int optionsIndex = meta->indexOfClassInfo("__meta__"); 241 | if (optionsIndex >= 0) { 242 | QMap options = parseOptions(meta->classInfo(optionsIndex).value()); 243 | QMapIterator option(options); 244 | while (option.hasNext()) { 245 | option.next(); 246 | if (option.key() == QLatin1String("db_table")) { 247 | d->table = option.value(); 248 | } else if (option.key() == QLatin1String("unique_together")) { 249 | d->uniqueTogether = option.value().toLatin1().split(','); 250 | } 251 | } 252 | } 253 | 254 | // 逐个属性解析 255 | const int count = meta->propertyCount(); 256 | for(int i = QObject::staticMetaObject.propertyCount(); i < count; ++i) 257 | { 258 | const QString typeName = QString::fromLatin1(meta->property(i).typeName()); 259 | if (!qstrcmp(meta->property(i).name(), "pk")) 260 | continue; 261 | 262 | // 解析字段属性 263 | bool autoIncrementOption = false; 264 | QString dbColumnOption; 265 | bool dbIndexOption = false; 266 | bool ignoreFieldOption = false; 267 | int maxLengthOption = 0; 268 | bool primaryKeyOption = false; 269 | bool nullOption = false; 270 | bool uniqueOption = false; 271 | bool blankOption = false; 272 | ForeignKeyConstraint deleteConstraint = NoAction; 273 | const int infoIndex = meta->indexOfClassInfo(meta->property(i).name()); 274 | if (infoIndex >= 0) 275 | { 276 | // 属性映射 277 | QMap options = parseOptions(meta->classInfo(infoIndex).value()); 278 | QMapIterator option(options); 279 | while (option.hasNext()) { 280 | option.next(); 281 | const QString key = option.key(); 282 | const QString value = option.value(); 283 | if (key == QLatin1String("auto_increment")) { 284 | autoIncrementOption = stringToBool(value); 285 | } else if (key == QLatin1String("db_column")) { 286 | dbColumnOption = value; 287 | } else if (key == QLatin1String("db_index")) { 288 | dbIndexOption = stringToBool(value); 289 | } else if (key == QLatin1String("ignore_field")) { 290 | ignoreFieldOption = stringToBool(value); 291 | } else if (key == QLatin1String("max_length")) { 292 | maxLengthOption = value.toInt(); 293 | } else if (key == QLatin1String("null")) { 294 | nullOption = stringToBool(value); 295 | } else if (key == QLatin1String("primary_key")) { 296 | primaryKeyOption = stringToBool(value); 297 | } else if (key == QLatin1String("unique")) { 298 | uniqueOption = stringToBool(value); 299 | } else if (key == QLatin1String("blank")) { 300 | blankOption = stringToBool(value); 301 | } else if (option.key() == "on_delete") { 302 | if (value.toLower() == "cascade") { 303 | deleteConstraint = Cascade; 304 | } else if (value.toLower() == "set_null") { 305 | deleteConstraint = SetNull; 306 | } else if (value.toLower() == "restrict") { 307 | deleteConstraint = Restrict; 308 | } 309 | } 310 | } 311 | } 312 | 313 | // 忽略字段 314 | if (ignoreFieldOption) 315 | continue; 316 | 317 | // 外键字段 318 | if (typeName.endsWith(QLatin1Char('*'))) { 319 | const QByteArray fkName = meta->property(i).name(); 320 | const QByteArray fkModel = typeName.left(typeName.size() - 1).toLatin1(); 321 | d->foreignFields.insert(fkName, fkModel); 322 | 323 | NOrmMetaField field; 324 | field.d->name = fkName + "_id"; 325 | field.d->type = QVariant::Int; 326 | field.d->foreignModel = fkModel; 327 | field.d->db_column = dbColumnOption.isEmpty() ? QString::fromLatin1(field.d->name) : dbColumnOption; 328 | field.d->index = true; 329 | field.d->null = nullOption; 330 | field.d->deleteConstraint = deleteConstraint; 331 | d->localFields << field; 332 | continue; 333 | } 334 | 335 | // 表字段 336 | NOrmMetaField field; 337 | field.d->name = meta->property(i).name(); 338 | field.d->type = meta->property(i).type(); 339 | field.d->db_column = dbColumnOption.isEmpty() ? QString::fromLatin1(field.d->name) : dbColumnOption; 340 | field.d->maxLength = maxLengthOption; 341 | field.d->null = nullOption; 342 | if (primaryKeyOption) { 343 | field.d->autoIncrement = autoIncrementOption; 344 | d->primaryKey = field.d->name; 345 | } else if (uniqueOption) { 346 | field.d->unique = true; 347 | } else if (blankOption) { 348 | field.d->blank = true; 349 | } else if (dbIndexOption) { 350 | field.d->index = true; 351 | } 352 | 353 | d->localFields << field; 354 | } 355 | 356 | // 自增主键 357 | if (d->primaryKey.isEmpty()) { 358 | NOrmMetaField field; 359 | field.d->name = "id"; 360 | field.d->type = QVariant::Int; 361 | field.d->db_column = QLatin1String("id"); 362 | field.d->null = false; 363 | field.d->autoIncrement = true; 364 | d->localFields.prepend(field); 365 | d->primaryKey = field.d->name; 366 | } 367 | 368 | } 369 | 370 | NOrmMetaModel::NOrmMetaModel(const NOrmMetaModel &other) : d(other.d) 371 | { 372 | } 373 | 374 | NOrmMetaModel::~NOrmMetaModel() 375 | { 376 | } 377 | 378 | QString NOrmMetaModel::className() const 379 | { 380 | return d->className; 381 | } 382 | 383 | bool NOrmMetaModel::isValid() const 384 | { 385 | return !d->table.isNull(); 386 | } 387 | 388 | NOrmMetaModel& NOrmMetaModel::operator=(const NOrmMetaModel& other) 389 | { 390 | d = other.d; 391 | return *this; 392 | } 393 | 394 | bool NOrmMetaModel::createTable() const 395 | { 396 | NOrmQuery createQuery(NOrm::database()); 397 | foreach (const QString &sql, createTableSql()) { 398 | if (!createQuery.exec(sql)) 399 | return false; 400 | } 401 | return true; 402 | } 403 | 404 | QStringList NOrmMetaModel::createTableSql() const 405 | { 406 | QSqlDatabase db = NOrm::database(); 407 | QSqlDriver *driver = db.driver(); 408 | NOrmDatabase::DatabaseType databaseType = NOrmDatabase::databaseType(db); 409 | 410 | QStringList queries; 411 | QStringList propSql; 412 | QStringList constraintSql; 413 | const QString quotedTable = db.driver()->escapeIdentifier(d->table, QSqlDriver::TableName); 414 | foreach (const NOrmMetaField &field, d->localFields) 415 | { 416 | QString fieldSql = driver->escapeIdentifier(field.column(), QSqlDriver::FieldName); 417 | fieldSql += " "; 418 | switch (field.d->type) { 419 | case QVariant::Bool: 420 | fieldSql += getBoolType(databaseType); 421 | break; 422 | case QVariant::ByteArray: 423 | fieldSql += getByteArrayType(databaseType, field.d->maxLength); 424 | break; 425 | case QVariant::Date: 426 | fieldSql += getDateType(databaseType); 427 | break; 428 | case QVariant::DateTime: 429 | fieldSql += getDateTimeType(databaseType); 430 | break; 431 | case QVariant::Double: 432 | fieldSql += getDoubleType(databaseType); 433 | break; 434 | case QVariant::Int: 435 | fieldSql += getIntType(databaseType); 436 | break; 437 | case QVariant::LongLong: 438 | fieldSql += getLongLongType(databaseType); 439 | break; 440 | case QVariant::String: 441 | fieldSql += getStringType(databaseType, field.d->maxLength); 442 | break; 443 | case QVariant::Time: 444 | fieldSql += getTimeType(databaseType); 445 | break; 446 | case QVariant::StringList: 447 | fieldSql += getStringListType(databaseType, field.d->maxLength); 448 | break; 449 | default: 450 | qWarning() << "Unhandled type" << field.d->type << "for property" << field.d->name; 451 | continue; 452 | } 453 | 454 | if (field.d->null) { 455 | fieldSql += " "; 456 | fieldSql += attributeNull(databaseType); 457 | } 458 | 459 | if (field.d->unique) { 460 | fieldSql += " "; 461 | fieldSql += attributeUnique(databaseType); 462 | } 463 | 464 | // primary key 465 | if (field.d->name == d->primaryKey) { 466 | fieldSql += " "; 467 | fieldSql += attributePrimaryKey(databaseType); 468 | } 469 | 470 | // auto-increment is backend specific 471 | if (field.d->autoIncrement) { 472 | fieldSql += " "; 473 | fieldSql += attributeAutoIncrement(databaseType,field); 474 | } 475 | 476 | // foreign key 477 | if (!field.d->foreignModel.isEmpty()) 478 | { 479 | const NOrmMetaModel foreignMeta = NOrm::metaModel(field.d->foreignModel); 480 | const NOrmMetaField foreignField = foreignMeta.localField("pk"); 481 | if (databaseType == NOrmDatabase::MySqlServer) { 482 | QString constraintName = QString::fromLatin1("FK_%1_%2").arg( 483 | field.column(), stringlist_digest(QStringList() << field.column() << d->table)); 484 | QString constraint = 485 | QString::fromLatin1("CONSTRAINT %1 FOREIGN KEY (%2) REFERENCES %3 (%4)").arg( 486 | driver->escapeIdentifier(constraintName, QSqlDriver::FieldName), 487 | driver->escapeIdentifier(field.column(), QSqlDriver::FieldName), 488 | driver->escapeIdentifier(foreignMeta.d->table, QSqlDriver::TableName), 489 | driver->escapeIdentifier(foreignField.column(), QSqlDriver::FieldName) 490 | ); 491 | 492 | if (field.d->deleteConstraint != NoAction) { 493 | constraint += " ON DELETE"; 494 | switch (field.d->deleteConstraint) { 495 | case Cascade: 496 | constraint += " CASCADE"; 497 | break; 498 | case SetNull: 499 | constraint += " SET NULL"; 500 | break; 501 | case Restrict: 502 | constraint += " RESTRICT"; 503 | break; 504 | default: 505 | break; 506 | } 507 | } 508 | constraintSql << constraint; 509 | } else { 510 | fieldSql += QString::fromLatin1(" REFERENCES %1 (%2)").arg( 511 | driver->escapeIdentifier(foreignMeta.d->table, QSqlDriver::TableName), 512 | driver->escapeIdentifier(foreignField.column(), QSqlDriver::FieldName)); 513 | 514 | if (databaseType == NOrmDatabase::MSSqlServer && 515 | field.d->deleteConstraint == Restrict) { 516 | qWarning("MSSQL does not support RESTRICT constraints"); 517 | break; 518 | } 519 | 520 | if (field.d->deleteConstraint != NoAction) { 521 | fieldSql += " ON DELETE"; 522 | switch (field.d->deleteConstraint) { 523 | case Cascade: 524 | fieldSql += " CASCADE"; 525 | break; 526 | case SetNull: 527 | fieldSql += " SET NULL"; 528 | break; 529 | case Restrict: 530 | fieldSql += " RESTRICT"; 531 | break; 532 | default: 533 | break; 534 | } 535 | } 536 | } 537 | 538 | if (databaseType == NOrmDatabase::PostgreSQL) 539 | fieldSql += " DEFERRABLE INITIALLY DEFERRED"; 540 | } 541 | propSql << fieldSql; 542 | } 543 | 544 | // add constraints if we need them 545 | if (!constraintSql.isEmpty()) 546 | propSql << constraintSql.join(QLatin1String(", ")); 547 | 548 | // unique contraints 549 | if (!d->uniqueTogether.isEmpty()) { 550 | QStringList columns; 551 | foreach (const QByteArray &name, d->uniqueTogether) { 552 | columns << driver->escapeIdentifier(localField(name).column(), QSqlDriver::FieldName); 553 | } 554 | propSql << QString::fromLatin1("UNIQUE (%2)").arg(columns.join(QLatin1String(", "))); 555 | } 556 | 557 | // create table 558 | queries << QString::fromLatin1("CREATE TABLE %1 (%2)").arg( 559 | quotedTable, 560 | propSql.join(QLatin1String(", "))); 561 | 562 | // create indices 563 | foreach (const NOrmMetaField &field, d->localFields) { 564 | if (field.d->index) { 565 | const QString indexName = d->table + QLatin1Char('_') 566 | + stringlist_digest(QStringList() << field.column()); 567 | queries << QString::fromLatin1("CREATE INDEX %1 ON %2 (%3)").arg( 568 | // FIXME : how should we escape an index name? 569 | driver->escapeIdentifier(indexName, QSqlDriver::FieldName), 570 | quotedTable, 571 | driver->escapeIdentifier(field.column(), QSqlDriver::FieldName)); 572 | } 573 | } 574 | 575 | return queries; 576 | } 577 | 578 | bool NOrmMetaModel::dropTable() const 579 | { 580 | QSqlDatabase db = NOrm::database(); 581 | if (!db.tables().contains(d->table)) 582 | return true; 583 | 584 | NOrmQuery query(db); 585 | return query.exec(QLatin1String("DROP TABLE ") + 586 | db.driver()->escapeIdentifier(d->table, QSqlDriver::TableName)); 587 | } 588 | 589 | QObject *NOrmMetaModel::foreignKey(const QObject *model, const char *name) const 590 | { 591 | // check the name is valid 592 | const QByteArray prop(name); 593 | if (!d->foreignFields.contains(prop)) { 594 | qWarning("NOrmMetaModel cannot get foreign model for invalid key '%s'", name); 595 | return nullptr; 596 | } 597 | 598 | QObject *foreign = model->property(prop + "_ptr").value(); 599 | if (!foreign) 600 | return nullptr; 601 | 602 | // if the foreign object was not loaded yet, do it now 603 | const QByteArray foreignClass = d->foreignFields[prop]; 604 | const NOrmMetaModel foreignMeta = NOrm::metaModel(foreignClass); 605 | const QVariant foreignPk = model->property(prop + "_id"); 606 | if (foreign->property(foreignMeta.primaryKey()) != foreignPk) 607 | { 608 | NOrmQuerySetPrivate qs(foreignClass); 609 | qs.addFilter(NOrmWhere(QLatin1String("pk"), NOrmWhere::Equals, foreignPk)); 610 | qs.sqlFetch(); 611 | if (qs.properties.size() != 1 || !qs.sqlLoad(foreign, 0)) 612 | return nullptr; 613 | } 614 | return foreign; 615 | } 616 | 617 | void NOrmMetaModel::setForeignKey(QObject *model, const char *name, QObject *value) const 618 | { 619 | // check the name is valid 620 | const QByteArray prop(name); 621 | if (!d->foreignFields.contains(prop)) { 622 | qWarning("NOrmMetaModel cannot set foreign model for invalid key '%s'", name); 623 | return; 624 | } 625 | 626 | QObject *old = model->property(prop + "_ptr").value(); 627 | if (old == value) 628 | return; 629 | 630 | // store the new pointer and update the foreign key 631 | model->setProperty(prop + "_ptr", qVariantFromValue(value)); 632 | if (value) { 633 | const NOrmMetaModel foreignMeta = NOrm::metaModel(d->foreignFields[prop]); 634 | model->setProperty(prop + "_id", value->property(foreignMeta.primaryKey())); 635 | } else { 636 | model->setProperty(prop + "_id", QVariant()); 637 | } 638 | } 639 | 640 | void NOrmMetaModel::load(QObject *model, const QVariantList &properties, int &pos, const QStringList &relatedFields) const 641 | { 642 | // process local fields 643 | foreach (const NOrmMetaField &field, d->localFields){ 644 | 645 | if(field.d->type == QVariant::StringList) { 646 | QVariant tmpObj; 647 | QStringList tmpStrList = properties.at(pos++).toStringList(); 648 | if(tmpStrList.size() > 0){ 649 | QString tmpStr = tmpStrList.first(); 650 | tmpObj = QVariant::fromValue(tmpStr.split(',')); 651 | } else { 652 | tmpObj = QVariant::fromValue(QString("")); 653 | } 654 | model->setProperty(field.d->name, tmpObj); 655 | } else { 656 | model->setProperty(field.d->name, properties.at(pos++)); 657 | } 658 | } 659 | 660 | // process foreign fields 661 | if (pos >= properties.size()) 662 | return; 663 | foreach (const QByteArray &fkName, d->foreignFields.keys()) 664 | { 665 | QString fkS(fkName); 666 | if ( relatedFields.contains(fkS) ) 667 | { 668 | QStringList nsl = relatedFields.filter(QRegExp("^" + fkS + "__")).replaceInStrings(QRegExp("^" + fkS + "__"),""); 669 | QObject *object = model->property(fkName + "_ptr").value(); 670 | if (object) 671 | { 672 | const NOrmMetaModel foreignMeta = NOrm::metaModel(d->foreignFields[fkName]); 673 | foreignMeta.load(object, properties, pos, nsl); 674 | } 675 | } 676 | 677 | if (relatedFields.isEmpty()) 678 | { 679 | QObject *object = model->property(fkName + "_ptr").value(); 680 | if (object) 681 | { 682 | const NOrmMetaModel foreignMeta = NOrm::metaModel(d->foreignFields[fkName]); 683 | foreignMeta.load(object, properties, pos); 684 | } 685 | } 686 | } 687 | } 688 | 689 | /*! 690 | Returns the foreign field mapping. 691 | */ 692 | QMap NOrmMetaModel::foreignFields() const 693 | { 694 | return d->foreignFields; 695 | } 696 | 697 | /*! 698 | Return the local field with the specified \a name. 699 | */ 700 | NOrmMetaField NOrmMetaModel::localField(const char *name) const 701 | { 702 | const QByteArray fieldName = strcmp(name, "pk") ? QByteArray(name) : d->primaryKey; 703 | foreach (const NOrmMetaField &field, d->localFields) { 704 | if (field.d->name == fieldName) 705 | return field; 706 | } 707 | return NOrmMetaField(); 708 | } 709 | 710 | QList NOrmMetaModel::localFields() const 711 | { 712 | return d->localFields; 713 | } 714 | 715 | QByteArray NOrmMetaModel::primaryKey() const 716 | { 717 | return d->primaryKey; 718 | } 719 | 720 | QString NOrmMetaModel::table() const 721 | { 722 | return d->table; 723 | } 724 | 725 | QString NOrmMetaModel::getBoolType(NOrmDatabase::DatabaseType databaseType) const 726 | { 727 | switch (databaseType) { 728 | case NOrmDatabase::UnknownDB: 729 | case NOrmDatabase::MySqlServer: 730 | case NOrmDatabase::Oracle: 731 | case NOrmDatabase::Sybase: 732 | case NOrmDatabase::SQLite: 733 | case NOrmDatabase::Interbase: 734 | case NOrmDatabase::DB2: 735 | return QString("bool"); 736 | case NOrmDatabase::PostgreSQL: 737 | return QString("boolean"); 738 | case NOrmDatabase::MSSqlServer: 739 | case NOrmDatabase::DaMeng: 740 | return QString("bit"); 741 | } 742 | return QString(); 743 | } 744 | 745 | QString NOrmMetaModel::getByteArrayType(NOrmDatabase::DatabaseType databaseType, int maxLength) const 746 | { 747 | QString sql; 748 | switch (databaseType) { 749 | case NOrmDatabase::UnknownDB: 750 | case NOrmDatabase::MySqlServer: 751 | case NOrmDatabase::Oracle: 752 | case NOrmDatabase::Sybase: 753 | case NOrmDatabase::SQLite: 754 | case NOrmDatabase::Interbase: 755 | case NOrmDatabase::DB2: 756 | case NOrmDatabase::DaMeng: 757 | sql += QLatin1String(" blob"); 758 | if (maxLength > 0) { 759 | sql += QLatin1Char('(') + QString::number(maxLength) + QLatin1Char(')'); 760 | } 761 | break; 762 | case NOrmDatabase::MSSqlServer: 763 | sql += QLatin1String("varbinary"); 764 | if (maxLength > 0) { 765 | sql += QLatin1Char('(') + QString::number(maxLength) + QLatin1Char(')'); 766 | } 767 | else { 768 | sql += QLatin1String("(max)"); 769 | } 770 | break; 771 | case NOrmDatabase::PostgreSQL: 772 | sql += QLatin1String("bytea"); 773 | break; 774 | } 775 | return sql; 776 | } 777 | 778 | QString NOrmMetaModel::getDateType(NOrmDatabase::DatabaseType databaseType) const 779 | { 780 | switch (databaseType) { 781 | case NOrmDatabase::UnknownDB: 782 | case NOrmDatabase::MSSqlServer: 783 | case NOrmDatabase::MySqlServer: 784 | case NOrmDatabase::PostgreSQL: 785 | case NOrmDatabase::Oracle: 786 | case NOrmDatabase::Sybase: 787 | case NOrmDatabase::SQLite: 788 | case NOrmDatabase::Interbase: 789 | case NOrmDatabase::DB2: 790 | case NOrmDatabase::DaMeng: 791 | return QString("date"); 792 | } 793 | return QString(); 794 | } 795 | 796 | QString NOrmMetaModel::getDateTimeType(NOrmDatabase::DatabaseType databaseType) const 797 | { 798 | QString fieldSql; 799 | switch (databaseType) { 800 | case NOrmDatabase::UnknownDB: 801 | case NOrmDatabase::MSSqlServer: 802 | case NOrmDatabase::MySqlServer: 803 | case NOrmDatabase::Oracle: 804 | case NOrmDatabase::Sybase: 805 | case NOrmDatabase::SQLite: 806 | case NOrmDatabase::Interbase: 807 | case NOrmDatabase::DB2: 808 | case NOrmDatabase::DaMeng: 809 | return QString("datetime"); 810 | case NOrmDatabase::PostgreSQL: 811 | return QLatin1String("timestamp"); 812 | } 813 | return QString(); 814 | } 815 | 816 | QString NOrmMetaModel::getDoubleType(NOrmDatabase::DatabaseType databaseType) const 817 | { 818 | switch (databaseType) { 819 | case NOrmDatabase::UnknownDB: 820 | case NOrmDatabase::MSSqlServer: 821 | case NOrmDatabase::MySqlServer: 822 | case NOrmDatabase::PostgreSQL: 823 | case NOrmDatabase::Oracle: 824 | case NOrmDatabase::Sybase: 825 | case NOrmDatabase::SQLite: 826 | case NOrmDatabase::Interbase: 827 | case NOrmDatabase::DB2: 828 | case NOrmDatabase::DaMeng: 829 | return QString("real"); 830 | } 831 | return QString(); 832 | } 833 | 834 | QString NOrmMetaModel::getIntType(NOrmDatabase::DatabaseType databaseType) const 835 | { 836 | switch (databaseType) { 837 | case NOrmDatabase::UnknownDB: 838 | case NOrmDatabase::MySqlServer: 839 | case NOrmDatabase::PostgreSQL: 840 | case NOrmDatabase::Oracle: 841 | case NOrmDatabase::Sybase: 842 | case NOrmDatabase::SQLite: 843 | case NOrmDatabase::Interbase: 844 | case NOrmDatabase::DB2: 845 | case NOrmDatabase::DaMeng: 846 | return QString("integer"); 847 | case NOrmDatabase::MSSqlServer: 848 | return QLatin1String("int"); 849 | } 850 | return QString(); 851 | } 852 | 853 | QString NOrmMetaModel::getLongLongType(NOrmDatabase::DatabaseType databaseType) const 854 | { 855 | switch (databaseType) { 856 | case NOrmDatabase::UnknownDB: 857 | case NOrmDatabase::MSSqlServer: 858 | case NOrmDatabase::MySqlServer: 859 | case NOrmDatabase::PostgreSQL: 860 | case NOrmDatabase::Oracle: 861 | case NOrmDatabase::Sybase: 862 | case NOrmDatabase::SQLite: 863 | case NOrmDatabase::Interbase: 864 | case NOrmDatabase::DB2: 865 | case NOrmDatabase::DaMeng: 866 | return QString("bigint"); 867 | } 868 | return QString(); 869 | } 870 | 871 | QString NOrmMetaModel::getStringType(NOrmDatabase::DatabaseType databaseType, int maxLength) const 872 | { 873 | switch (databaseType) { 874 | case NOrmDatabase::UnknownDB: 875 | case NOrmDatabase::MySqlServer: 876 | case NOrmDatabase::PostgreSQL: 877 | case NOrmDatabase::Oracle: 878 | case NOrmDatabase::Sybase: 879 | case NOrmDatabase::SQLite: 880 | case NOrmDatabase::Interbase: 881 | case NOrmDatabase::DB2: 882 | case NOrmDatabase::DaMeng: 883 | if (maxLength > 0) { 884 | return QLatin1String("varchar(") + QString::number(maxLength) + QLatin1Char(')'); 885 | } else { 886 | return QLatin1String("text"); 887 | } 888 | case NOrmDatabase::MSSqlServer: 889 | if (maxLength > 0) { 890 | return QLatin1String("nvarchar(") + QString::number(maxLength) + QLatin1Char(')'); 891 | } else { 892 | return QLatin1String("nvarchar(max)"); 893 | } 894 | } 895 | return QString(); 896 | } 897 | 898 | QString NOrmMetaModel::getTimeType(NOrmDatabase::DatabaseType databaseType) const 899 | { 900 | switch (databaseType) { 901 | case NOrmDatabase::UnknownDB: 902 | case NOrmDatabase::MSSqlServer: 903 | case NOrmDatabase::MySqlServer: 904 | case NOrmDatabase::PostgreSQL: 905 | case NOrmDatabase::Oracle: 906 | case NOrmDatabase::Sybase: 907 | case NOrmDatabase::SQLite: 908 | case NOrmDatabase::Interbase: 909 | case NOrmDatabase::DB2: 910 | case NOrmDatabase::DaMeng: 911 | return QString("time"); 912 | } 913 | return QString(); 914 | } 915 | 916 | QString NOrmMetaModel::getStringListType(NOrmDatabase::DatabaseType databaseType, int maxLength) const 917 | { 918 | return getStringType(databaseType,maxLength); 919 | } 920 | 921 | QString NOrmMetaModel::attributeNull(NOrmDatabase::DatabaseType databaseType) const 922 | { 923 | switch (databaseType) { 924 | case NOrmDatabase::UnknownDB: 925 | case NOrmDatabase::MSSqlServer: 926 | case NOrmDatabase::MySqlServer: 927 | case NOrmDatabase::PostgreSQL: 928 | case NOrmDatabase::Oracle: 929 | case NOrmDatabase::Sybase: 930 | case NOrmDatabase::SQLite: 931 | case NOrmDatabase::Interbase: 932 | case NOrmDatabase::DB2: 933 | case NOrmDatabase::DaMeng: 934 | return QString("NOT NULL"); 935 | } 936 | return QString(); 937 | } 938 | 939 | QString NOrmMetaModel::attributeUnique(NOrmDatabase::DatabaseType databaseType) const 940 | { 941 | switch (databaseType) { 942 | case NOrmDatabase::UnknownDB: 943 | case NOrmDatabase::MSSqlServer: 944 | case NOrmDatabase::MySqlServer: 945 | case NOrmDatabase::PostgreSQL: 946 | case NOrmDatabase::Oracle: 947 | case NOrmDatabase::Sybase: 948 | case NOrmDatabase::SQLite: 949 | case NOrmDatabase::Interbase: 950 | case NOrmDatabase::DB2: 951 | case NOrmDatabase::DaMeng: 952 | return QString("UNIQUE"); 953 | } 954 | return QString(); 955 | } 956 | 957 | QString NOrmMetaModel::attributePrimaryKey(NOrmDatabase::DatabaseType databaseType) const 958 | { 959 | switch (databaseType) { 960 | case NOrmDatabase::UnknownDB: 961 | case NOrmDatabase::MSSqlServer: 962 | case NOrmDatabase::MySqlServer: 963 | case NOrmDatabase::PostgreSQL: 964 | case NOrmDatabase::Oracle: 965 | case NOrmDatabase::Sybase: 966 | case NOrmDatabase::SQLite: 967 | case NOrmDatabase::Interbase: 968 | case NOrmDatabase::DB2: 969 | case NOrmDatabase::DaMeng: 970 | return QString("PRIMARY KEY"); 971 | } 972 | return QString(); 973 | } 974 | 975 | QString NOrmMetaModel::attributeAutoIncrement(NOrmDatabase::DatabaseType databaseType, const NOrmMetaField &field) const 976 | { 977 | QSqlDatabase db = NOrm::database(); 978 | QSqlDriver *driver = db.driver(); 979 | switch (databaseType) { 980 | case NOrmDatabase::UnknownDB: 981 | case NOrmDatabase::Oracle: 982 | case NOrmDatabase::Sybase: 983 | case NOrmDatabase::Interbase: 984 | case NOrmDatabase::DB2: 985 | return QString(); 986 | case NOrmDatabase::MSSqlServer: 987 | case NOrmDatabase::DaMeng: 988 | return QLatin1String("IDENTITY(1,1)"); 989 | case NOrmDatabase::MySqlServer: 990 | return QLatin1String("AUTO_INCREMENT"); 991 | case NOrmDatabase::PostgreSQL: 992 | return driver->escapeIdentifier(field.column(), QSqlDriver::FieldName) + QLatin1String(" serial PRIMARY KEY"); 993 | case NOrmDatabase::SQLite: 994 | return QLatin1String("AUTOINCREMENT"); 995 | } 996 | return QString(); 997 | } 998 | 999 | bool NOrmMetaModel::remove(QObject *model) const 1000 | { 1001 | const QVariant pk = model->property(d->primaryKey); 1002 | NOrmQuerySetPrivate qs(model->metaObject()->className()); 1003 | qs.addFilter(NOrmWhere(QLatin1String("pk"), NOrmWhere::Equals, pk)); 1004 | return qs.sqlDelete(); 1005 | } 1006 | 1007 | bool NOrmMetaModel::save(QObject *model) const 1008 | { 1009 | // find primary key 1010 | const NOrmMetaField primaryKey = localField("pk"); 1011 | const QVariant pk = model->property(d->primaryKey); 1012 | if (!pk.isNull() && !(primaryKey.d->type == QVariant::Int && !pk.toInt())) 1013 | { 1014 | QSqlDatabase db = NOrm::database(); 1015 | NOrmQuery query(db); 1016 | query.prepare(QString::fromLatin1("SELECT 1 AS a FROM %1 WHERE %2 = ?").arg( 1017 | db.driver()->escapeIdentifier(d->table, QSqlDriver::FieldName), 1018 | db.driver()->escapeIdentifier(primaryKey.column(), QSqlDriver::FieldName))); 1019 | query.addBindValue(pk); 1020 | if (query.exec() && query.next()) 1021 | { 1022 | // prepare data 1023 | QVariantMap fields; 1024 | foreach (const NOrmMetaField &field, d->localFields) { 1025 | if (field.d->name != d->primaryKey) { 1026 | const QVariant value = model->property(field.d->name); 1027 | fields.insert(QString::fromLatin1(field.d->name), field.toDatabase(value)); 1028 | } 1029 | } 1030 | 1031 | // perform UPDATE 1032 | NOrmQuerySetPrivate qs(model->metaObject()->className()); 1033 | qs.addFilter(NOrmWhere(QLatin1String("pk"), NOrmWhere::Equals, pk)); 1034 | return qs.sqlUpdate(fields) != -1; 1035 | } 1036 | } 1037 | 1038 | // prepare data 1039 | QVariantMap fields; 1040 | foreach (const NOrmMetaField &field, d->localFields) { 1041 | if (!field.d->autoIncrement) { 1042 | const QVariant value = model->property(field.d->name); 1043 | fields.insert(field.name(), field.toDatabase(value)); 1044 | } 1045 | } 1046 | 1047 | // perform INSERT 1048 | NOrmQuerySetPrivate qs(model->metaObject()->className()); 1049 | if (primaryKey.d->autoIncrement) { 1050 | // fetch autoincrement pk 1051 | QVariant insertId; 1052 | if (!qs.sqlInsert(fields, &insertId)) 1053 | return false; 1054 | model->setProperty(d->primaryKey, insertId); 1055 | } else { 1056 | if (!qs.sqlInsert(fields)) 1057 | return false; 1058 | } 1059 | return true; 1060 | } 1061 | 1062 | --------------------------------------------------------------------------------