diff --git a/.codecov.yml b/.codecov.yml new file mode 100644 index 0000000..23c0459 --- /dev/null +++ b/.codecov.yml @@ -0,0 +1,20 @@ +codecov: + require_ci_to_pass: yes + +coverage: + precision: 2 + round: down + range: "70...100" + +parsers: + gcov: + branch_detection: + conditional: yes + loop: yes + method: no + macro: no + +comment: + layout: "reach,diff,flags,files,footer" + behavior: default + require_changes: no \ No newline at end of file diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..338b88a --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,20 @@ +name: CMongo Documentation + +on: + push: + branches: + - 'main' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout reposistory + uses: actions/checkout@v2 + + - name: Upload Documentation to Wiki + uses: SwiftDocOrg/github-wiki-publish-action@v1 + with: + path: "docs" + env: + GH_PERSONAL_ACCESS_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml new file mode 100644 index 0000000..4b79d23 --- /dev/null +++ b/.github/workflows/pre-release.yml @@ -0,0 +1,39 @@ +name: CMongo Pre-Release + +on: + push: + branches: + - '*beta' + +jobs: + build: + name: Create Pre-Release + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: Read version file + id: get_version + run: echo "::set-output name=version::$(cat version.txt)" + + - name: Read changelog + id: get_changes + run: | + changes=$(cat CHANGELOG.md) + changes="${changes//'%'/'%25'}" + changes="${changes//$'\n'/'%0A'}" + changes="${changes//$'\r'/'%0D'}" + echo "::set-output name=changes::$changes" + + - name: Create Pre-Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ steps.get_version.outputs.version }} + release_name: Beta ${{ steps.get_version.outputs.version }} + body: ${{ steps.get_changes.outputs.changes }} + draft: false + prerelease: true \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..fd083b5 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,39 @@ +name: CMongo Release + +on: + push: + branches: + - 'main' + +jobs: + build: + name: Create Release + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Read version file + id: get_version + run: echo "::set-output name=version::$(cat version.txt)" + + - name: Read changelog + id: get_changes + run: | + changes=$(cat CHANGELOG.md) + changes="${changes//'%'/'%25'}" + changes="${changes//$'\n'/'%0A'}" + changes="${changes//$'\r'/'%0D'}" + echo "::set-output name=changes::$changes" + + - name: Create Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: ${{ steps.get_version.outputs.version }} + release_name: Release ${{ steps.get_version.outputs.version }} + body: ${{ steps.get_changes.outputs.changes }} + draft: false + prerelease: false \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..a35fc82 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,39 @@ +name: CMongo Tests + +on: + pull_request: + branches: + - '*' + +jobs: + coverage: + name: CMongo tests + runs-on: ubuntu-latest + container: + image: ermiry/mongoc:buildev + steps: + - name: Checkout + uses: actions/checkout@v2 + + # compile + - name: Compile Sources + run: make TYPE=test COVERAGE=1 + - name: Compile Tests + run: make TYPE=test COVERAGE=1 test + + # run + - name: Unit Tests + run: bash test/run.sh + + - name: Coverage + run: make coverage + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v1 + with: + token: ${{ secrets.CODECOV_TOKEN }} + directory: ./coverage/ + flags: unittests + name: build + fail_ci_if_error: true + # verbose: true diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml new file mode 100644 index 0000000..450a471 --- /dev/null +++ b/.github/workflows/version.yml @@ -0,0 +1,29 @@ +name: CMongo Version Check + +on: + push: + branches: + - '*beta' + - 'main' + pull_request: + branches: + - '*beta' + - 'main' + +jobs: + build: + name: CMongo build + runs-on: ubuntu-latest + container: + image: ermiry/mongoc:buildev + steps: + - name: Checkout code + uses: actions/checkout@v2 + + - name: gcc make + run: make + - name: gcc tests + run: make test + + - name: Check version + run: LD_LIBRARY_PATH=bin ./test/bin/version \ No newline at end of file diff --git a/.gitignore b/.gitignore index 8f84944..a6aea71 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,8 @@ bin objs +coverage + keys data diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..dc534c2 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +- Refactored mongo_count_docs_internal () +- Removed bson_copy (query) in find all methods +- Refactored crud methods to print errors in dev \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..ded10ad --- /dev/null +++ b/build.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# ensure a clean build +make clean + +# gcc +printf "gcc make\n\n" +make -j8 +printf "\n\ngcc test\n\n" +make test -j8 diff --git a/coverage.sh b/coverage.sh new file mode 100644 index 0000000..77a0dd6 --- /dev/null +++ b/coverage.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +# ensure a clean build +make clean + +# gcc +printf "gcc make\n\n" +make TYPE=test -j8 +printf "\n\ngcc test\n\n" +make TYPE=test test -j8 + +# run +bash test/run.sh + +make coverage diff --git a/docs/Home.md b/docs/Home.md new file mode 100644 index 0000000..c45998c --- /dev/null +++ b/docs/Home.md @@ -0,0 +1 @@ +Welcome to the cmongo wiki! diff --git a/docs/_Footer.md b/docs/_Footer.md new file mode 100644 index 0000000..6ffc593 --- /dev/null +++ b/docs/_Footer.md @@ -0,0 +1 @@ +Ermiry - Never Stop Creating \ No newline at end of file diff --git a/examples/pool.c b/examples/pool.c new file mode 100644 index 0000000..66ed23b --- /dev/null +++ b/examples/pool.c @@ -0,0 +1,99 @@ +#include + +#include + +#include + +static pthread_mutex_t mutex; +static bool in_shutdown = false; + +static void *worker (void *data) { + + mongoc_client_pool_t *pool = data; + mongoc_client_t *client; + bson_t ping = BSON_INITIALIZER; + bson_error_t error; + bool r; + + BSON_APPEND_INT32 (&ping, "ping", 1); + + while (true) { + client = mongoc_client_pool_pop (pool); + + r = mongoc_client_command_simple ( + client, "admin", &ping, NULL, NULL, &error + ); + + if (!r) { + fprintf (stderr, "%s\n", error.message); + } + + mongoc_client_pool_push (pool, client); + + pthread_mutex_lock (&mutex); + if (in_shutdown || !r) { + pthread_mutex_unlock (&mutex); + break; + } + + pthread_mutex_unlock (&mutex); + } + + bson_destroy (&ping); + return NULL; + +} + +int main (int argc, char **argv) { + + const char *uri_string = "mongodb://127.0.0.1/?appname=pool-example"; + mongoc_uri_t *uri; + bson_error_t error; + mongoc_client_pool_t *pool; + pthread_t threads[10]; + unsigned i; + void *ret; + + pthread_mutex_init (&mutex, NULL); + mongoc_init (); + + if (argc > 1) { + uri_string = argv[1]; + } + + uri = mongoc_uri_new_with_error (uri_string, &error); + if (!uri) { + fprintf (stderr, + "failed to parse URI: %s\n" + "error message: %s\n", + uri_string, + error.message + ); + + return EXIT_FAILURE; + } + + pool = mongoc_client_pool_new (uri); + mongoc_client_pool_set_error_api (pool, 2); + + for (i = 0; i < 10; i++) { + pthread_create (&threads[i], NULL, worker, pool); + } + + sleep (10); + pthread_mutex_lock (&mutex); + in_shutdown = true; + pthread_mutex_unlock (&mutex); + + for (i = 0; i < 10; i++) { + pthread_join (threads[i], &ret); + } + + mongoc_client_pool_destroy (pool); + mongoc_uri_destroy (uri); + + mongoc_cleanup (); + + return EXIT_SUCCESS; + +} \ No newline at end of file diff --git a/include/cmongo/collections.h b/include/cmongo/collections.h index d0c8458..0e88397 100644 --- a/include/cmongo/collections.h +++ b/include/cmongo/collections.h @@ -3,18 +3,15 @@ #include +#include "cmongo/mongo.h" + #ifdef __cplusplus extern "C" { #endif -// opens handle to a mongo collection in the db -extern mongoc_collection_t *mongo_collection_get ( - const char *coll_name -); - // drops a collection deleting all of its data // retuns 0 on success, 1 on error -extern int mongo_collection_drop ( +CMONGO_EXPORT int mongo_collection_drop ( mongoc_collection_t *collection ); diff --git a/include/cmongo/crud.h b/include/cmongo/crud.h index 702c6cf..458e6fc 100644 --- a/include/cmongo/crud.h +++ b/include/cmongo/crud.h @@ -1,30 +1,36 @@ #ifndef _CMONGO_CRUD_H_ #define _CMONGO_CRUD_H_ -#include #include +#include +#include "cmongo/model.h" #include "cmongo/select.h" +#include "cmongo/types.h" #ifdef __cplusplus extern "C" { #endif -typedef void (*mongo_parser)(void *model, const bson_t *doc); +#pragma region count // counts the docs in a collection by a matching query -extern int64_t mongo_count_docs ( - mongoc_collection_t *collection, bson_t *query +CMONGO_EXPORT int64_t mongo_count_docs ( + const CMongoModel *model, bson_t *query ); +#pragma endregion + +#pragma region find + // returns true if 1 or more documents matches the query, false if no matches -extern bool mongo_check ( - mongoc_collection_t *collection, bson_t *query +CMONGO_EXPORT bool mongo_check ( + const CMongoModel *model, bson_t *query ); // generates an opts doc that can be used to better work with find methods // primarily used to query with projection (select) options -extern bson_t *mongo_find_generate_opts ( +CMONGO_EXPORT bson_t *mongo_find_generate_opts ( const CMongoSelect *select ); @@ -33,39 +39,77 @@ extern bson_t *mongo_find_generate_opts ( // _id is true by default and should not be incldued // returns a cursor (should be destroyed) that can be used to traverse the matching documents // query gets destroyed, select list remains the same -extern mongoc_cursor_t *mongo_find_all_cursor ( - mongoc_collection_t *collection, +CMONGO_EXPORT mongoc_cursor_t *mongo_find_all_cursor ( + const CMongoModel *model, bson_t *query, const CMongoSelect *select, uint64_t *n_docs ); // uses a query to find all matching docs with the specified options // query gets destroyed, options remain the same -extern mongoc_cursor_t *mongo_find_all_cursor_with_opts ( - mongoc_collection_t *collection, +CMONGO_EXPORT mongoc_cursor_t *mongo_find_all_cursor_with_opts ( + const CMongoModel *model, bson_t *query, const bson_t *opts ); // use a query to find all matching documents // an empty query will return all the docs in a collection -extern const bson_t **mongo_find_all ( - mongoc_collection_t *collection, +CMONGO_EXPORT const bson_t **mongo_find_all ( + const CMongoModel *model, bson_t *query, const CMongoSelect *select, uint64_t *n_docs ); // correctly destroys an array of docs got from mongo_find_all () -extern void mongo_find_all_destroy_docs ( +CMONGO_EXPORT void mongo_find_all_destroy_docs ( bson_t **docs, uint64_t count ); +// returns a new string in relaxed extended JSON format +// with all matching objects inside an array +// query gets destroyed, opts are kept the same +CMONGO_EXPORT unsigned int mongo_find_all_to_json ( + const CMongoModel *model, + bson_t *query, const bson_t *opts, + const char *array_name, + char **json, size_t *json_len +); + +// works like mongo_find_all_to_json () +// but also populates the specified object +CMONGO_EXPORT unsigned int mongo_find_all_populate_object_to_json ( + const CMongoModel *model, + const char *from, const char *local_field, + const char *array_name, + char **json, size_t *json_len +); + +// works like mongo_find_all_to_json () +// but also populates the specified objects array +CMONGO_EXPORT unsigned int mongo_find_all_populate_array_to_json ( + const CMongoModel *model, + const char *from, const char *local_field, + const char *array_name, + char **json, size_t *json_len +); + // uses a query to find one doc with the specified options // query gets destroyed, opts are kept the same // returns 0 on success, 1 on error -extern unsigned int mongo_find_one_with_opts ( - mongoc_collection_t *collection, +CMONGO_EXPORT unsigned int mongo_find_one_with_opts ( + const CMongoModel *model, bson_t *query, const bson_t *opts, - void *model, const mongo_parser model_parser + void *output +); + +// works like mongo_find_one_with_opts () +// creates a new string with the result in relaxed extended JSON format +// query gets destroyed, opts are kept the same +// returns 0 on success, 1 on error +CMONGO_EXPORT unsigned int mongo_find_one_with_opts_to_json ( + const CMongoModel *model, + bson_t *query, const bson_t *opts, + char **json, size_t *json_len ); // uses a query to find one doc @@ -73,57 +117,180 @@ extern unsigned int mongo_find_one_with_opts ( // _id is true by default and should not be incldued // query gets destroyed, select structure remains the same // returns 0 on success, 1 on error -extern unsigned int mongo_find_one ( - mongoc_collection_t *collection, +CMONGO_EXPORT unsigned int mongo_find_one ( + const CMongoModel *model, bson_t *query, const CMongoSelect *select, - void *model, const mongo_parser model_parser + void *output +); + +// performs an aggregation in the model's collection +// to match an object by its oid and then lookup & undwind +// the selected field using its _id +// returns 0 on success, 1 on error +CMONGO_EXPORT unsigned int mongo_find_one_populate_object ( + const CMongoModel *model, + const bson_oid_t *oid, + const char *from, const char *local_field, + void *output +); + +// works like mongo_find_one_populate_object () +// but converts the result into a json string +// returns 0 on success, 1 on error +CMONGO_EXPORT unsigned int mongo_find_one_populate_object_to_json ( + const CMongoModel *model, + const bson_oid_t *oid, + const char *from, const char *local_field, + char **json, size_t *json_len +); + +// performs an aggregation in the model's collection +// to match an object by its oid and then lookup (populate) +// the selected array by searching by the object's oids +// returns 0 on success, 1 on error +CMONGO_EXPORT unsigned int mongo_find_one_populate_array ( + const CMongoModel *model, + const bson_oid_t *oid, + const char *from, const char *local_field, + void *output +); + +// works like mongo_find_one_populate_array () +// but converts the result into a json string +// returns 0 on success, 1 on error +CMONGO_EXPORT unsigned int mongo_find_one_populate_array_to_json ( + const CMongoModel *model, + const bson_oid_t *oid, + const char *from, const char *local_field, + char **json, size_t *json_len ); +// works like mongo_find_one_populate_array () +// but also populates an object inside an objects array +// and converts the result into a json string +// returns 0 on success, 1 on error +CMONGO_EXPORT unsigned int mongo_find_one_populate_array_with_object_to_json ( + const CMongoModel *model, + const bson_oid_t *oid, + const char *from, const char *array_name, const char *local_field, + char **json, size_t *json_len +); + +// returns a new string in relaxed extended JSON format +// created with the result of an aggregation that represents +// how a single object's array gets populated +// pipeline gets destroyed, opts are kept the same +// returns 0 on success, 1 on error +CMONGO_EXPORT unsigned int mongo_find_one_custom_populate_array_to_json ( + const CMongoModel *model, + bson_t *pipeline, + char **json, size_t *json_len +); + +#pragma endregion + +#pragma region insert + // inserts a document into a collection // destroys document // returns 0 on success, 1 on error -extern int mongo_insert_one ( - mongoc_collection_t *collection, bson_t *doc +CMONGO_EXPORT unsigned int mongo_insert_one ( + const CMongoModel *model, bson_t *doc ); // inserts many documents into a collection // docs are NOT deleted after the operation // returns 0 on success, 1 on error -extern int mongo_insert_many ( - mongoc_collection_t *collection, +CMONGO_EXPORT unsigned int mongo_insert_many ( + const CMongoModel *model, const bson_t **docs, size_t n_docs ); +#pragma endregion + +#pragma region update + // updates a doc by a matching query with the new values // destroys query and update documents // returns 0 on success, 1 on error -extern int mongo_update_one ( - mongoc_collection_t *collection, +CMONGO_EXPORT unsigned int mongo_update_one ( + const CMongoModel *model, bson_t *query, bson_t *update ); // updates all the query matching documents // destroys the query and the update documents // returns 0 on success, 1 on error -extern int mongo_update_many ( - mongoc_collection_t *collection, +CMONGO_EXPORT unsigned int mongo_update_many ( + const CMongoModel *model, bson_t *query, bson_t *update ); +#pragma endregion + +#pragma region delete + // deletes one matching document by a query // destroys the query document // returns 0 on success, 1 on error -extern int mongo_delete_one ( - mongoc_collection_t *collection, bson_t *query +CMONGO_EXPORT unsigned int mongo_delete_one ( + const CMongoModel *model, bson_t *query ); // deletes all the query matching documents // destroys the query // returns 0 on success, 1 on error -extern int mongo_delete_many ( - mongoc_collection_t *collection, bson_t *query +CMONGO_EXPORT unsigned int mongo_delete_many ( + const CMongoModel *model, bson_t *query +); + +#pragma endregion + +#pragma region aggregation + +// performs an aggregation in the model's collection +// the pipeline document gets destroyed +// returns a cursor with the aggregation's result +CMONGO_EXPORT mongoc_cursor_t *mongo_perform_aggregation_with_opts ( + const CMongoModel *model, + mongoc_query_flags_t flags, + const bson_t *opts, + bson_t *pipeline +); + +// works like mongo_perform_aggregation_with_opts () +// but sets flags to 0 and opts to NULL +CMONGO_EXPORT mongoc_cursor_t *mongo_perform_aggregation ( + const CMongoModel *model, bson_t *pipeline +); + +// finds one document by matching id with custom pipeline +// usefull when trying to perform custom populate methods +// returns 0 on success, 1 on error +CMONGO_EXPORT unsigned int mongo_perform_single_aggregation ( + const CMongoModel *model, bson_t *pipeline, void *output ); +// works like mongo_perform_single_aggregation_to_json () +// but outputs the result directly into a json string +// returns 0 on success, 1 on error +CMONGO_EXPORT unsigned int mongo_perform_single_aggregation_to_json ( + const CMongoModel *model, bson_t *pipeline, + char **json, size_t *json_len +); + +// works like mongo_perform_aggregation () +// but outputs all the aggregation's result into an array +// useful when aggregation returns many matches +// returns 0 on success, 1 on error +CMONGO_EXPORT unsigned int mongo_perform_aggregation_to_json ( + const CMongoModel *model, bson_t *pipeline, + const char *array_name, + char **json, size_t *json_len +); + +#pragma endregion + #ifdef __cplusplus } #endif diff --git a/include/cmongo/model.h b/include/cmongo/model.h new file mode 100644 index 0000000..aaeea7c --- /dev/null +++ b/include/cmongo/model.h @@ -0,0 +1,43 @@ +#ifndef _CMONGO_MODELS_H_ +#define _CMONGO_MODELS_H_ + +#include + +#include + +#include "cmongo/config.h" + +#define CMONGO_COLLNAME_SIZE 128 + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*mongo_parser)(void *model, const bson_t *doc); + +typedef struct CMongoModel { + + size_t collname_len; + char collname[CMONGO_COLLNAME_SIZE]; + + mongo_parser model_parser; + +} CMongoModel; + +CMONGO_PUBLIC CMongoModel *cmongo_model_new (void); + +CMONGO_PUBLIC void cmongo_model_delete (void *model_ptr); + +CMONGO_EXPORT CMongoModel *cmongo_model_create ( + const char *collname +); + +CMONGO_EXPORT void cmongo_model_set_parser ( + CMongoModel *model, const mongo_parser model_parser +); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/include/cmongo/mongo.h b/include/cmongo/mongo.h index bf2d30e..78ae75d 100644 --- a/include/cmongo/mongo.h +++ b/include/cmongo/mongo.h @@ -4,14 +4,22 @@ #include #include +#include "cmongo/config.h" + +#define CMONGO_DB_NAME_SIZE 128 +#define CMONGO_HOST_SIZE 128 + +#define CMONGO_USERNAME_SIZE 128 +#define CMONGO_PASSWORD_SIZE 128 + +#define CMONGO_APP_NAME_SIZE 128 + +#define CMONGO_URI_SIZE 1024 + #ifdef __cplusplus extern "C" { #endif -extern mongoc_client_t *client; - -extern char *db_name; - typedef enum MongoStatus { MONGO_STATUS_DISCONNECTED = 0, @@ -19,37 +27,65 @@ typedef enum MongoStatus { } MongoStatus; -extern MongoStatus mongo_get_status (void); +typedef struct Mongo { + + MongoStatus status; + + size_t db_name_len; + char db_name[CMONGO_DB_NAME_SIZE]; + + size_t host_len; + char host[CMONGO_HOST_SIZE]; + unsigned int port; + + size_t username_len; + char username[CMONGO_USERNAME_SIZE]; + size_t password_len; + char password[CMONGO_PASSWORD_SIZE]; -extern void mongo_set_host (const char *h); + size_t app_name_len; + char app_name[CMONGO_APP_NAME_SIZE]; -extern void mongo_set_port (const char *p); + size_t uri_len; + char uri[CMONGO_URI_SIZE]; -extern void mongo_set_username (const char *u); + mongoc_client_pool_t *pool; -extern void mongo_set_password (const char *pswd); +} Mongo; -extern void mongo_set_db_name (const char *name); +CMONGO_PRIVATE Mongo mongo; -extern void mongo_set_app_name (const char *name); +CMONGO_EXPORT MongoStatus mongo_get_status (void); -extern void mongo_set_uri (const char *uri); +CMONGO_EXPORT void mongo_set_db_name (const char *db_name); -// generates a new uri string with the set values (username, password, host, port & db name) +CMONGO_EXPORT void mongo_set_host (const char *host); + +CMONGO_EXPORT void mongo_set_port (const unsigned int port); + +CMONGO_EXPORT void mongo_set_username (const char *username); + +CMONGO_EXPORT void mongo_set_password (const char *pswd); + +CMONGO_EXPORT void mongo_set_app_name (const char *app_name); + +CMONGO_EXPORT void mongo_set_uri (const char *uri); + +// generates a new uri string with the set values +// (username, password, host, port & db name) // that can be used to set as the uri for a new connection -// returns the newly uri string (that should be freed) on success -// returns NULL on error -extern char *mongo_uri_generate (void); +// returns 0 on success 1 on error +CMONGO_EXPORT unsigned int mongo_uri_generate (void); + +// connect to the mongo db with db name +CMONGO_EXPORT unsigned int mongo_connect (void); // pings the db to test for a success connection // returns 0 on success, 1 on error -extern int mongo_ping_db (void); - -// connect to the mongo db with db name -extern int mongo_connect (void); +CMONGO_EXPORT unsigned int mongo_ping_db (void); // disconnects from the db -extern void mongo_disconnect (void); +CMONGO_EXPORT void mongo_disconnect (void); #ifdef __cplusplus } diff --git a/include/cmongo/select.h b/include/cmongo/select.h index 55b44a8..6a2d46f 100644 --- a/include/cmongo/select.h +++ b/include/cmongo/select.h @@ -6,6 +6,7 @@ #include "cmongo/config.h" #define CMONGO_SELECT_FIELD_LEN 256 +#define CMONGO_SELECT_FIELDS_SIZE 32 #ifdef __cplusplus extern "C" { @@ -13,49 +14,24 @@ extern "C" { typedef struct CMongoSelectField { - struct CMongoSelectField *prev; - - char value[CMONGO_SELECT_FIELD_LEN]; size_t len; - - struct CMongoSelectField *next; + char value[CMONGO_SELECT_FIELD_LEN]; } CMongoSelectField; -CMONGO_EXPORT CMongoSelectField *cmongo_select_field_create ( - const char *value -); - typedef struct CMongoSelect { - size_t size; - - CMongoSelectField *start; - CMongoSelectField *end; + size_t n_fields; + CMongoSelectField fields[CMONGO_SELECT_FIELDS_SIZE]; } CMongoSelect; -#define cmongo_select_for_each(select) \ - for (CMongoSelectField *field = select->start; field; field = field->next) - -#define cmongo_select_for_each_backwards(select) \ - for (CMongoSelectField *field = select->end; field; field = field->prev) - CMONGO_EXPORT CMongoSelect *cmongo_select_new (void); -CMONGO_EXPORT bool cmongo_select_is_empty ( - const CMongoSelect *select -); - -CMONGO_EXPORT bool cmongo_select_is_not_empty ( - const CMongoSelect *select -); - // adds a new field to the select list // returns 0 on success, 1 on error CMONGO_EXPORT int cmongo_select_insert_field ( - CMongoSelect *select, - CMongoSelectField *field + CMongoSelect *select, const char *field ); CMONGO_EXPORT void cmongo_select_delete ( diff --git a/include/cmongo/types.h b/include/cmongo/types.h index 0bef352..cdc3b74 100644 --- a/include/cmongo/types.h +++ b/include/cmongo/types.h @@ -3,18 +3,27 @@ #include +#include "cmongo/config.h" + +#define BSON_OID_BUFFER_SIZE 32 +#define BSON_ARRAY_BUFFER_SIZE 16 + #ifdef __cplusplus extern "C" { #endif -extern bson_oid_t *bson_oid_new (void); +CMONGO_PUBLIC bson_oid_t *bson_oid_new (void); -extern void bson_oid_delete (void *bson_oid_t_ptr); +CMONGO_PUBLIC void bson_oid_delete (void *bson_oid_t_ptr); -extern bson_oid_t *bson_oid_create ( +CMONGO_PUBLIC bson_oid_t *bson_oid_create ( const bson_oid_t *original_oid ); +CMONGO_PUBLIC void bson_oid_print (const bson_oid_t *oid); + +CMONGO_PUBLIC void bson_print_as_json (const bson_t *document); + #ifdef __cplusplus } #endif diff --git a/include/cmongo/version.h b/include/cmongo/version.h index 6a95120..009a3a2 100644 --- a/include/cmongo/version.h +++ b/include/cmongo/version.h @@ -1,24 +1,26 @@ #ifndef _CMONGO_VERSION_H_ #define _CMONGO_VERSION_H_ -#define CMONGO_VERSION "1.0" -#define CMONGO_VERSION_NAME "Release 1.0" -#define CMONGO_VERSION_DATE "04/01/2021" -#define CMONGO_VERSION_TIME "23:28 CST" -#define CMONGO_VERSION_AUTHOR "Erick Salas" +#include "cmongo/config.h" + +#define CMONGO_VERSION "1.0b-13" +#define CMONGO_VERSION_NAME "Beta 1.0b-13" +#define CMONGO_VERSION_DATE "19/06/2021" +#define CMONGO_VERSION_TIME "14:51 CST" +#define CMONGO_VERSION_AUTHOR "Erick Salas" #ifdef __cplusplus extern "C" { #endif // print full cmongo version information -extern void cmongo_version_print_full (void); +CMONGO_EXPORT void cmongo_version_print_full (void); // print the version id -extern void cmongo_version_print_version_id (void); +CMONGO_EXPORT void cmongo_version_print_version_id (void); // print the version name -extern void cmongo_version_print_version_name (void); +CMONGO_EXPORT void cmongo_version_print_version_name (void); #ifdef __cplusplus } diff --git a/makefile b/makefile index 1a1bba5..9ab3880 100644 --- a/makefile +++ b/makefile @@ -1,18 +1,39 @@ TYPE := development +COVERAGE := 0 + +DEBUG := 0 + SLIB := libcmongo.so -MONGOC := -l mongoc-1.0 -l bson-1.0 +all: directories $(SLIB) + +directories: + @mkdir -p $(TARGETDIR) + @mkdir -p $(BUILDDIR) + +install: $(SLIB) + install -m 644 ./bin/libcmongo.so /usr/local/lib/ + cp -R ./include/cmongo /usr/local/include + +uninstall: + rm /usr/local/lib/libcmongo.so + rm -r /usr/local/include/cmongo + +MONGOC := -l bson-1.0 -l mongoc-1.0 MONGOC_INC := -I /usr/local/include/libbson-1.0 -I /usr/local/include/libmongoc-1.0 DEFINES := -D _GNU_SOURCE +DEVELOPMENT := -D CMONGO_DEBUG + CC := gcc GCCVGTEQ8 := $(shell expr `gcc -dumpversion | cut -f1 -d.` \>= 8) SRCDIR := src INCDIR := include + BUILDDIR := objs TARGETDIR := bin @@ -20,10 +41,12 @@ SRCEXT := c DEPEXT := d OBJEXT := o +COVDIR := coverage +COVEXT := gcov + # common flags -# -Wconversion -COMMON := -march=native \ - -Wall -Wno-unknown-pragmas \ +# -Wconversion -march=native +COMMON := -Wall -Wno-unknown-pragmas \ -Wfloat-equal -Wdouble-promotion -Wint-to-pointer-cast -Wwrite-strings \ -Wtype-limits -Wsign-compare -Wmissing-field-initializers \ -Wuninitialized -Wmaybe-uninitialized -Wempty-body \ @@ -34,9 +57,14 @@ COMMON := -march=native \ CFLAGS := $(DEFINES) ifeq ($(TYPE), development) - CFLAGS += -g -fasynchronous-unwind-tables + CFLAGS += -g -fasynchronous-unwind-tables $(DEVELOPMENT) else ifeq ($(TYPE), test) CFLAGS += -g -fasynchronous-unwind-tables -D_FORTIFY_SOURCE=2 -fstack-protector -O2 + ifeq ($(COVERAGE), 1) + CFLAGS += -fprofile-arcs -ftest-coverage + endif +else ifeq ($(TYPE), beta) + CFLAGS += -g -D_FORTIFY_SOURCE=2 -O2 else CFLAGS += -D_FORTIFY_SOURCE=2 -O2 endif @@ -58,29 +86,20 @@ endif CFLAGS += -fPIC $(COMMON) LIB := -L /usr/local/lib $(MONGOC) + +ifeq ($(TYPE), test) + ifeq ($(COVERAGE), 1) + LIB += -lgcov --coverage + endif +endif + INC := -I $(INCDIR) -I /usr/local/include $(MONGOC_INC) INCDEP := -I $(INCDIR) SOURCES := $(shell find $(SRCDIR) -type f -name *.$(SRCEXT)) OBJECTS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(OBJEXT))) -all: directories $(SLIB) - -install: $(SLIB) - install -m 644 ./bin/libcmongo.so /usr/local/lib/ - cp -R ./include/cmongo /usr/local/include - -uninstall: - rm /usr/local/lib/libcmongo.so - rm -r /usr/local/include/cmongo - -directories: - @mkdir -p $(TARGETDIR) - @mkdir -p $(BUILDDIR) - -clean: - @$(RM) -rf $(BUILDDIR) - @$(RM) -rf $(TARGETDIR) +SRCCOVS := $(patsubst $(SRCDIR)/%,$(BUILDDIR)/%,$(SOURCES:.$(SRCEXT)=.$(SRCEXT).$(COVEXT))) # pull in dependency info for *existing* .o files -include $(OBJECTS:.$(OBJEXT)=.$(DEPEXT)) @@ -98,4 +117,89 @@ $(BUILDDIR)/%.$(OBJEXT): $(SRCDIR)/%.$(SRCEXT) @sed -e 's/.*://' -e 's/\\$$//' < $(BUILDDIR)/$*.$(DEPEXT).tmp | fmt -1 | sed -e 's/^ *//' -e 's/$$/:/' >> $(BUILDDIR)/$*.$(DEPEXT) @rm -f $(BUILDDIR)/$*.$(DEPEXT).tmp -.PHONY: all clean \ No newline at end of file +# tests +TESTDIR := test +TESTBUILD := $(TESTDIR)/objs +TESTTARGET := $(TESTDIR)/bin +TESTCOVDIR := $(COVDIR)/test + +TESTFLAGS := -g $(DEFINES) -Wall -Wno-unknown-pragmas -Wno-format + +ifeq ($(TYPE), test) + ifeq ($(COVERAGE), 1) + TESTFLAGS += -fprofile-arcs -ftest-coverage + endif +endif + +TESTLIBS := $(PTHREAD) -L ./bin -l cmongo + +TESTLIBS += -Wl,-rpath=./$(TARGETDIR) -L ./$(TARGETDIR) -l cmongo + +ifeq ($(TYPE), test) + ifeq ($(COVERAGE), 1) + TESTLIBS += -lgcov --coverage + endif +endif + +TESTINC := -I $(INCDIR) -I ./$(TESTDIR) + +TESTS := $(shell find $(TESTDIR) -type f -name *.$(SRCEXT)) +TESTOBJS := $(patsubst $(TESTDIR)/%,$(TESTBUILD)/%,$(TESTS:.$(SRCEXT)=.$(OBJEXT))) + +TESTCOVS := $(patsubst $(TESTDIR)/%,$(TESTBUILD)/%,$(TESTS:.$(SRCEXT)=.$(SRCEXT).$(COVEXT))) + +test: $(TESTOBJS) + @mkdir -p ./$(TESTTARGET) + @mkdir -p ./$(TESTTARGET) + $(CC) $(TESTINC) ./$(TESTBUILD)/model.o -o ./$(TESTTARGET)/model $(TESTLIBS) + $(CC) $(TESTINC) ./$(TESTBUILD)/select.o -o ./$(TESTTARGET)/select $(TESTLIBS) + $(CC) $(TESTINC) ./$(TESTBUILD)/version.o -o ./$(TESTTARGET)/version $(TESTLIBS) + +# compile tests +$(TESTBUILD)/%.$(OBJEXT): $(TESTDIR)/%.$(SRCEXT) + @mkdir -p $(dir $@) + $(CC) $(TESTFLAGS) $(INC) $(TESTLIBS) -c -o $@ $< + @$(CC) $(TESTFLAGS) $(INCDEP) -MM $(TESTDIR)/$*.$(SRCEXT) > $(TESTBUILD)/$*.$(DEPEXT) + @cp -f $(TESTBUILD)/$*.$(DEPEXT) $(TESTBUILD)/$*.$(DEPEXT).tmp + @sed -e 's|.*:|$(TESTBUILD)/$*.$(OBJEXT):|' < $(TESTBUILD)/$*.$(DEPEXT).tmp > $(TESTBUILD)/$*.$(DEPEXT) + @sed -e 's/.*://' -e 's/\\$$//' < $(TESTBUILD)/$*.$(DEPEXT).tmp | fmt -1 | sed -e 's/^ *//' -e 's/$$/:/' >> $(TESTBUILD)/$*.$(DEPEXT) + @rm -f $(TESTBUILD)/$*.$(DEPEXT).tmp + +#coverage +COVOBJS := $(SRCCOVS) $(TESTCOVS) + +test-coverage: $(COVOBJS) + +coverage-init: + @mkdir -p ./$(COVDIR) + @mkdir -p ./$(TESTCOVDIR) + +coverage: coverage-init test-coverage + +# get lib coverage reports +$(BUILDDIR)/%.$(SRCEXT).$(COVEXT): $(SRCDIR)/%.$(SRCEXT) + @mkdir -p ./$(COVDIR)/$(dir $<) + gcov -r $< --object-directory $(dir $@) + mv $(notdir $@) ./$(COVDIR)/$<.gcov + +# get tests coverage reports +$(TESTBUILD)/%.$(SRCEXT).$(COVEXT): $(TESTDIR)/%.$(SRCEXT) + gcov -r $< --object-directory $(dir $@) + mv $(notdir $@) ./$(TESTCOVDIR) + +clear: clean-objects clean-tests clean-coverage + +clean: clear + @$(RM) -rf $(TARGETDIR) + +clean-objects: + @$(RM) -rf $(BUILDDIR) + +clean-tests: + @$(RM) -rf $(TESTBUILD) + @$(RM) -rf $(TESTTARGET) + +clean-coverage: + @$(RM) -rf $(COVDIR) + +.PHONY: all clean clear test coverage diff --git a/production.sh b/production.sh new file mode 100644 index 0000000..1260a66 --- /dev/null +++ b/production.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +# ensure a clean build +make clean + +# main sources +printf "gcc make\n\n" +make TYPE=production -j8 diff --git a/src/cmongo/collections.c b/src/cmongo/collections.c index b302cdc..404b994 100644 --- a/src/cmongo/collections.c +++ b/src/cmongo/collections.c @@ -5,13 +5,6 @@ #include "cmongo/mongo.h" -// opens handle to a mongo collection in the db -mongoc_collection_t *mongo_collection_get (const char *coll_name) { - - return mongoc_client_get_collection (client, db_name, coll_name); - -} - // drops a collection deleting all of its data // retuns 0 on success, 1 on error int mongo_collection_drop (mongoc_collection_t *collection) { diff --git a/src/cmongo/crud.c b/src/cmongo/crud.c index 14b984f..2ab9dca 100644 --- a/src/cmongo/crud.c +++ b/src/cmongo/crud.c @@ -2,36 +2,78 @@ #include #include -#include #include +#include #include "cmongo/crud.h" +#include "cmongo/model.h" +#include "cmongo/mongo.h" #include "cmongo/select.h" +#include "cmongo/types.h" + +#define CMONGO_UNWIND_VALUE_SIZE 64 #ifdef __cplusplus #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" #endif +#pragma region count + // counts the docs in a collection by a matching query -int64_t mongo_count_docs ( +static int64_t mongo_count_docs_internal ( mongoc_collection_t *collection, bson_t *query ) { - int64_t retval = 0; + int64_t count = 0; + + #ifdef CMONGO_DEBUG + bson_error_t error = { 0 }; + + count = mongoc_collection_count_documents ( + collection, query, NULL, NULL, NULL, &error + ); - if (collection && query) { - bson_error_t error = { 0 }; - retval = mongoc_collection_count_documents ( - collection, query, NULL, NULL, NULL, &error + if (count < 0) { + (void) fprintf ( + stderr, "[MONGO][ERROR]: %s\n", error.message ); - if (retval < 0) { - (void) fprintf ( - stderr, "[MONGO][ERROR]: %s", error.message + count = 0; + } + #else + count = mongoc_collection_count_documents ( + collection, query, NULL, NULL, NULL, NULL + ); + #endif + + return count; + +} + +// counts the docs in a collection by a matching query +int64_t mongo_count_docs ( + const CMongoModel *model, bson_t *query +) { + + int64_t retval = 0; + + if (model && query) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname ); - retval = 0; + if (collection) { + retval = mongo_count_docs_internal ( + collection, query + ); + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); } bson_destroy (query); @@ -41,19 +83,35 @@ int64_t mongo_count_docs ( } +#pragma endregion + +#pragma region find + // returns true if 1 or more documents matches the query // returns false if no matches bool mongo_check ( - mongoc_collection_t *collection, bson_t *query + const CMongoModel *model, bson_t *query ) { bool retval = false; - if (collection && query) { - bson_error_t error = { 0 }; - retval = mongoc_collection_count_documents ( - collection, query, NULL, NULL, NULL, &error - ); + if (model && query) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + if (mongo_count_docs_internal (collection, query) > 0) { + retval = true; + } + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } bson_destroy (query); } @@ -78,9 +136,12 @@ bson_t *mongo_find_generate_opts ( (void) bson_append_bool (&projection_doc, "_id", -1, true); - cmongo_select_for_each (select) { + for (size_t idx = 0; idx < select->n_fields; idx++) { (void) bson_append_bool ( - &projection_doc, field->value, field->len, true + &projection_doc, + select->fields[idx].value, + select->fields[idx].len, + true ); } @@ -98,7 +159,7 @@ bson_t *mongo_find_generate_opts ( // returns a cursor (should be destroyed) that can be used to traverse the matching documents // query gets destroyed, select list remains the same mongoc_cursor_t *mongo_find_all_cursor ( - mongoc_collection_t *collection, + const CMongoModel *model, bson_t *query, const CMongoSelect *select, uint64_t *n_docs ) { @@ -106,19 +167,35 @@ mongoc_cursor_t *mongo_find_all_cursor ( mongoc_cursor_t *cursor = NULL; *n_docs = 0; - if (collection && query) { - uint64_t count = mongo_count_docs (collection, bson_copy (query)); - if (count > 0) { - bson_t *opts = mongo_find_generate_opts (select); - - cursor = mongoc_collection_find_with_opts ( - collection, query, opts, NULL + if (model && query) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname ); - *n_docs = count; + if (collection) { + uint64_t count = (uint64_t) mongo_count_docs_internal ( + collection, query + ); + + if (count > 0) { + bson_t *opts = mongo_find_generate_opts (select); - if (opts) bson_destroy (opts); - bson_destroy (query); + cursor = mongoc_collection_find_with_opts ( + collection, query, opts, NULL + ); + + *n_docs = count; + + if (opts) bson_destroy (opts); + bson_destroy (query); + } + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); } } @@ -129,16 +206,29 @@ mongoc_cursor_t *mongo_find_all_cursor ( // uses a query to find all matching docs with the specified options // query gets destroyed, options remain the same mongoc_cursor_t *mongo_find_all_cursor_with_opts ( - mongoc_collection_t *collection, + const CMongoModel *model, bson_t *query, const bson_t *opts ) { mongoc_cursor_t *cursor = NULL; - if (collection && query) { - cursor = mongoc_collection_find_with_opts ( - collection, query, opts, NULL - ); + if (model && query) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + cursor = mongoc_collection_find_with_opts ( + collection, query, opts, NULL + ); + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } bson_destroy (query); } @@ -147,45 +237,76 @@ mongoc_cursor_t *mongo_find_all_cursor_with_opts ( } -// use a query to find all matching documents -// an empty query will return all the docs in a collection -const bson_t **mongo_find_all ( +static const bson_t **mongo_find_all_internal ( mongoc_collection_t *collection, bson_t *query, const CMongoSelect *select, uint64_t *n_docs ) { const bson_t **retval = NULL; - *n_docs = 0; - if (collection && query) { - uint64_t count = mongo_count_docs (collection, bson_copy (query)); - if (count > 0) { - retval = (const bson_t **) calloc (count, sizeof (bson_t *)); - for (uint64_t i = 0; i < count; i++) retval[i] = bson_new (); + uint64_t count = (uint64_t) mongo_count_docs_internal (collection, query); + if (count > 0) { + retval = (const bson_t **) calloc (count, sizeof (bson_t *)); + for (uint64_t i = 0; i < count; i++) retval[i] = bson_new (); - bson_t *opts = mongo_find_generate_opts (select); + bson_t *opts = mongo_find_generate_opts (select); - mongoc_cursor_t *cursor = mongoc_collection_find_with_opts ( - collection, query, opts, NULL - ); + mongoc_cursor_t *cursor = mongoc_collection_find_with_opts ( + collection, query, opts, NULL + ); - uint64_t i = 0; - const bson_t *doc = NULL; - while (mongoc_cursor_next (cursor, &doc)) { - // add the matching doc into our retval array - bson_copy_to (doc, (bson_t *) retval[i]); - i++; - } + uint64_t i = 0; + const bson_t *doc = NULL; + while (mongoc_cursor_next (cursor, &doc)) { + // add the matching doc into our retval array + bson_copy_to (doc, (bson_t *) retval[i]); + i++; + } + + *n_docs = count; + + mongoc_cursor_destroy (cursor); + + if (opts) bson_destroy (opts); + } + + return retval; + +} + +// use a query to find all matching documents +// an empty query will return all the docs in a collection +const bson_t **mongo_find_all ( + const CMongoModel *model, + bson_t *query, const CMongoSelect *select, + uint64_t *n_docs +) { + + const bson_t **retval = NULL; + *n_docs = 0; - *n_docs = count; + if (model && query) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); - mongoc_cursor_destroy (cursor); + if (collection) { + retval = mongo_find_all_internal ( + collection, + query, select, + n_docs + ); - if (opts) bson_destroy (opts); + mongoc_collection_destroy (collection); + } - bson_destroy (query); + mongoc_client_pool_push (mongo.pool, client); } + + bson_destroy (query); } return retval; @@ -206,201 +327,357 @@ void mongo_find_all_destroy_docs ( } -static unsigned int mongo_find_one_internal ( - mongoc_collection_t *collection, - bson_t *query, const bson_t *opts, - void *model, const mongo_parser model_parser +static char *mongo_find_all_to_json_internal ( + mongoc_cursor_t *cursor, + const char *array_name, size_t *json_len ) { - unsigned int retval = 1; + char *json = NULL; - mongoc_cursor_t *cursor = mongoc_collection_find_with_opts ( - collection, query, opts, NULL - ); + bson_t *doc = bson_new (); + if (doc) { + char buf[BSON_ARRAY_BUFFER_SIZE] = { 0 }; + const char *key = NULL; + size_t keylen = 0; - if (cursor) { - (void) mongoc_cursor_set_limit (cursor, 1); + bson_t json_array = BSON_INITIALIZER; + (void) bson_append_array_begin (doc, array_name, (int) strlen (array_name), &json_array); - const bson_t *doc = NULL; - if (mongoc_cursor_next (cursor, &doc)) { - model_parser (model, doc); - retval = 0; + int i = 0; + const bson_t *object_doc = NULL; + while (mongoc_cursor_next (cursor, &object_doc)) { + keylen = bson_uint32_to_string (i, &key, buf, BSON_ARRAY_BUFFER_SIZE); + (void) bson_append_document (&json_array, key, (int) keylen, object_doc); + + bson_destroy ((bson_t *) object_doc); + + i++; } - mongoc_cursor_destroy (cursor); + (void) bson_append_array_end (doc, &json_array); + + json = bson_as_relaxed_extended_json (doc, json_len); } - return retval; + return json; } -// uses a query to find one doc with the specified options +// returns a new string in relaxed extended JSON format +// with all matching objects inside an array // query gets destroyed, opts are kept the same -// returns 0 on success, 1 on error -unsigned int mongo_find_one_with_opts ( - mongoc_collection_t *collection, +unsigned int mongo_find_all_to_json ( + const CMongoModel *model, bson_t *query, const bson_t *opts, - void *model, const mongo_parser model_parser + const char *array_name, + char **json, size_t *json_len ) { unsigned int retval = 1; - if (collection && query && model && model_parser) { - retval = mongo_find_one_internal ( - collection, - query, opts, - model, model_parser + mongoc_cursor_t *cursor = mongo_find_all_cursor_with_opts ( + model, + query, opts + ); + + if (cursor) { + *json = mongo_find_all_to_json_internal ( + cursor, + array_name, json_len ); - bson_destroy (query); + mongoc_cursor_destroy (cursor); + + retval = 0; } return retval; } -// uses a query to find one doc -// select is a dlist of strings used for document projection, -// _id is true by default and should not be incldued -// query gets destroyed, select structure remains the same -// returns 0 on success, 1 on error -unsigned int mongo_find_one ( - mongoc_collection_t *collection, - bson_t *query, const CMongoSelect *select, - void *model, const mongo_parser model_parser +static bson_t *mongo_find_all_populate_object_pipeline ( + const char *from, const char *local_field +) { + + char unwind[CMONGO_UNWIND_VALUE_SIZE] = { 0 }; + (void) snprintf ( + unwind, CMONGO_UNWIND_VALUE_SIZE - 1, + "$%s", local_field + ); + + bson_t *pipeline = BCON_NEW ( + "pipeline", + "[", + "{", + "$lookup", "{", + "from", BCON_UTF8 (from), + "localField", BCON_UTF8 (local_field), + "foreignField", BCON_UTF8 ("_id"), + "as", BCON_UTF8 (local_field), + "}", + "}", + + "{", + "$unwind", BCON_UTF8 (unwind), + "}", + "]" + ); + + return pipeline; + +} + +// works like mongo_find_all_to_json () +// but also populates the specified object +unsigned int mongo_find_all_populate_object_to_json ( + const CMongoModel *model, + const char *from, const char *local_field, + const char *array_name, + char **json, size_t *json_len ) { unsigned int retval = 1; - if (collection && query && model && model_parser) { - bson_t *opts = mongo_find_generate_opts (select); + if (model && from && local_field) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); - retval = mongo_find_one_internal ( - collection, - query, opts, - model, model_parser - ); + if (collection) { + bson_t *pipeline = mongo_find_all_populate_object_pipeline ( + from, local_field + ); - if (opts) bson_destroy (opts); + if (pipeline) { + mongoc_cursor_t *cursor = mongoc_collection_aggregate ( + collection, 0, pipeline, NULL, NULL + ); - bson_destroy (query); + if (cursor) { + *json = mongo_find_all_to_json_internal ( + cursor, array_name, json_len + ); + + mongoc_cursor_destroy (cursor); + + retval = 0; + } + + bson_destroy (pipeline); + } + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } } return retval; } -// inserts a document into a collection -// destroys document -// returns 0 on success, 1 on error -int mongo_insert_one ( - mongoc_collection_t *collection, bson_t *doc +static inline bson_t *mongo_find_all_populate_array_pipeline ( + const char *from, const char *local_field ) { - int retval = 1; + bson_t *pipeline = BCON_NEW ( + "pipeline", + "[", + "{", + "$lookup", "{", + "from", BCON_UTF8 (from), + "localField", BCON_UTF8 (local_field), + "foreignField", BCON_UTF8 ("_id"), + "as", BCON_UTF8 (local_field), + "}", + "}", + "]" + ); + + return pipeline; - if (collection && doc) { - bson_error_t error = { 0 }; +} - retval = mongoc_collection_insert_one ( - collection, doc, NULL, NULL, &error - ) ? 0 : 1; +// works like mongo_find_all_to_json () +// but also populates the specified objects array +unsigned int mongo_find_all_populate_array_to_json ( + const CMongoModel *model, + const char *from, const char *local_field, + const char *array_name, + char **json, size_t *json_len +) { - bson_destroy (doc); + unsigned int retval = 1; + + if (model && from && local_field) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + bson_t *pipeline = mongo_find_all_populate_array_pipeline ( + from, local_field + ); + + if (pipeline) { + mongoc_cursor_t *cursor = mongoc_collection_aggregate ( + collection, 0, pipeline, NULL, NULL + ); + + if (cursor) { + *json = mongo_find_all_to_json_internal ( + cursor, array_name, json_len + ); + + mongoc_cursor_destroy (cursor); + + retval = 0; + } + + bson_destroy (pipeline); + } + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } } return retval; } -// inserts many documents into a collection -// docs are NOT deleted after the operation -// returns 0 on success, 1 on error -int mongo_insert_many ( +static unsigned int mongo_find_one_internal ( mongoc_collection_t *collection, - const bson_t **docs, size_t n_docs + bson_t *query, const bson_t *opts, + void *output, const mongo_parser model_parser ) { - int retval = 1; + unsigned int retval = 1; + + mongoc_cursor_t *cursor = mongoc_collection_find_with_opts ( + collection, query, opts, NULL + ); + + if (cursor) { + (void) mongoc_cursor_set_limit (cursor, 1); - if (collection && docs) { - bson_error_t error = { 0 }; + const bson_t *doc = NULL; + if (mongoc_cursor_next (cursor, &doc)) { + model_parser (output, doc); + retval = 0; + } - retval = mongoc_collection_insert_many ( - collection, docs, n_docs, NULL, NULL, &error - ) ? 0 : 1; + mongoc_cursor_destroy (cursor); } return retval; } - -// updates a doc by a matching query with the new values -// destroys query and update documents +// uses a query to find one doc with the specified options +// query gets destroyed, opts are kept the same // returns 0 on success, 1 on error -int mongo_update_one ( - mongoc_collection_t *collection, - bson_t *query, bson_t *update +unsigned int mongo_find_one_with_opts ( + const CMongoModel *model, + bson_t *query, const bson_t *opts, + void *output ) { - int retval = 1; + unsigned int retval = 1; + + if (model && query && output) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + retval = mongo_find_one_internal ( + collection, + query, opts, + output, model->model_parser + ); - if (collection && query && update) { - bson_error_t error = { 0 }; + mongoc_collection_destroy (collection); + } - retval = mongoc_collection_update_one ( - collection, query, update, NULL, NULL, &error - ) ? 0 : 1; + mongoc_client_pool_push (mongo.pool, client); + } bson_destroy (query); - bson_destroy (update); } return retval; } -// updates all the query matching documents -// destroys the query and the update documents -// returns 0 on success, 1 on error -int mongo_update_many ( +static unsigned int mongo_find_one_internal_to_json ( mongoc_collection_t *collection, - bson_t *query, bson_t *update + bson_t *query, const bson_t *opts, + char **json, size_t *json_len ) { - int retval = 0; + unsigned int retval = 1; + + mongoc_cursor_t *cursor = mongoc_collection_find_with_opts ( + collection, query, opts, NULL + ); - if (collection && query && update) { - bson_error_t error = { 0 }; + if (cursor) { + (void) mongoc_cursor_set_limit (cursor, 1); - retval = mongoc_collection_update_many ( - collection, query, update, NULL, NULL, &error - ) ? 0 : 1; + const bson_t *doc = NULL; + if (mongoc_cursor_next (cursor, &doc)) { + *json = bson_as_relaxed_extended_json (doc, json_len); + retval = 0; + } - bson_destroy (query); - bson_destroy (update); + mongoc_cursor_destroy (cursor); } return retval; } -// deletes one matching document by a query -// destroys the query document +// works like mongo_find_one_with_opts () +// creates a new string with the result in relaxed extended JSON format +// query gets destroyed, opts are kept the same // returns 0 on success, 1 on error -int mongo_delete_one ( - mongoc_collection_t *collection, bson_t *query +unsigned int mongo_find_one_with_opts_to_json ( + const CMongoModel *model, + bson_t *query, const bson_t *opts, + char **json, size_t *json_len ) { - int retval = 0; + unsigned int retval = 1; + + if (model && query && json && json_len) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + retval = mongo_find_one_internal_to_json ( + collection, + query, opts, + json, json_len + ); - if (collection && query) { - bson_error_t error = { 0 }; + mongoc_collection_destroy (collection); + } - retval = mongoc_collection_delete_one ( - collection, query, NULL, NULL, &error - ) ? 0 : 1; + mongoc_client_pool_push (mongo.pool, client); + } bson_destroy (query); } @@ -409,21 +686,42 @@ int mongo_delete_one ( } -// deletes all the query matching documents -// destroys the query +// uses a query to find one doc +// select is a dlist of strings used for document projection, +// _id is true by default and should not be incldued +// query gets destroyed, select structure remains the same // returns 0 on success, 1 on error -int mongo_delete_many ( - mongoc_collection_t *collection, bson_t *query +unsigned int mongo_find_one ( + const CMongoModel *model, + bson_t *query, const CMongoSelect *select, + void *output ) { - int retval = 0; + unsigned int retval = 1; + + if (model && query && output) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + bson_t *opts = mongo_find_generate_opts (select); + + retval = mongo_find_one_internal ( + collection, + query, opts, + output, model->model_parser + ); - if (collection && query) { - bson_error_t error = { 0 }; + if (opts) bson_destroy (opts); - retval = mongoc_collection_delete_many ( - collection, query, NULL, NULL, &error - ) ? 0 : 1; + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } bson_destroy (query); } @@ -432,6 +730,945 @@ int mongo_delete_many ( } -#ifdef __cplusplus +static bson_t *mongo_find_one_populate_object_pipeline ( + const bson_oid_t *oid, + const char *from, const char *local_field +) { + + char unwind[CMONGO_UNWIND_VALUE_SIZE] = { 0 }; + (void) snprintf ( + unwind, CMONGO_UNWIND_VALUE_SIZE - 1, + "$%s", local_field + ); + + bson_t *pipeline = BCON_NEW ( + "pipeline", + "[", + "{", + "$match", "{", "store", BCON_OID (oid), "}", + "}", + "{", + "$lookup", "{", + "from", BCON_UTF8 (from), + "localField", BCON_UTF8 (local_field), + "foreignField", BCON_UTF8 ("_id"), + "as", BCON_UTF8 (local_field), + "}", + "}", + + "{", + "$unwind", BCON_UTF8 (unwind), + "}", + "]" + ); + + return pipeline; + +} + +static unsigned int mongo_find_one_aggregate_internal ( + mongoc_collection_t *collection, + const bson_t *pipeline, + void *output, const mongo_parser model_parser +) { + + unsigned int retval = 1; + + mongoc_cursor_t *cursor = mongoc_collection_aggregate ( + collection, 0, pipeline, NULL, NULL + ); + + if (cursor) { + const bson_t *doc = NULL; + if (mongoc_cursor_next (cursor, &doc)) { + model_parser (output, doc); + retval = 0; + } + + mongoc_cursor_destroy (cursor); + } + + return retval; + +} + +// performs an aggregation in the model's collection +// to match an object by its oid and then lookup & undwind +// the selected field using its _id +// returns 0 on success, 1 on error +unsigned int mongo_find_one_populate_object ( + const CMongoModel *model, + const bson_oid_t *oid, + const char *from, const char *local_field, + void *output +) { + + unsigned int retval = 1; + + if (model && oid && from && local_field && output) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + bson_t *pipeline = mongo_find_one_populate_object_pipeline ( + oid, from, local_field + ); + + if (pipeline) { + retval = mongo_find_one_aggregate_internal ( + collection, pipeline, + output, model->model_parser + ); + + bson_destroy (pipeline); + } + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } + } + + return retval; + +} + +// works like mongo_find_one_populate_object () +// but converts the result into a json string +// returns 0 on success, 1 on error +unsigned int mongo_find_one_populate_object_to_json ( + const CMongoModel *model, + const bson_oid_t *oid, + const char *from, const char *local_field, + char **json, size_t *json_len +) { + + unsigned int retval = 1; + + if (model && oid && from && local_field) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + bson_t *pipeline = mongo_find_one_populate_object_pipeline ( + oid, from, local_field + ); + + if (pipeline) { + mongoc_cursor_t *cursor = mongoc_collection_aggregate ( + collection, 0, pipeline, NULL, NULL + ); + + if (cursor) { + const bson_t *doc = NULL; + if (mongoc_cursor_next (cursor, &doc)) { + *json = bson_as_relaxed_extended_json (doc, json_len); + } + + mongoc_cursor_destroy (cursor); + + retval = 0; + } + + bson_destroy (pipeline); + } + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } + } + + return retval; + +} + +static inline bson_t *mongo_find_one_populate_array_pipeline ( + const bson_oid_t *oid, + const char *from, const char *local_field +) { + + bson_t *pipeline = BCON_NEW ( + "pipeline", + "[", + "{", + "$match", "{", "_id", BCON_OID (oid), "}", + "}", + "{", + "$lookup", "{", + "from", BCON_UTF8 (from), + "localField", BCON_UTF8 (local_field), + "foreignField", BCON_UTF8 ("_id"), + "as", BCON_UTF8 (local_field), + "}", + "}", + "]" + ); + + return pipeline; + +} + +// performs an aggregation in the model's collection +// to match an object by its oid and then lookup (populate) +// the selected array by searching by the object's oids +// returns 0 on success, 1 on error +unsigned int mongo_find_one_populate_array ( + const CMongoModel *model, + const bson_oid_t *oid, + const char *from, const char *local_field, + void *output +) { + + unsigned int retval = 1; + + if (model && oid && from && local_field && output) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + bson_t *pipeline = mongo_find_one_populate_array_pipeline ( + oid, from, local_field + ); + + if (pipeline) { + retval = mongo_find_one_aggregate_internal ( + collection, pipeline, + output, model->model_parser + ); + + bson_destroy (pipeline); + } + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } + } + + return retval; + +} + +// works like mongo_find_one_populate_array () +// but converts the result into a json string +// returns 0 on success, 1 on error +unsigned int mongo_find_one_populate_array_to_json ( + const CMongoModel *model, + const bson_oid_t *oid, + const char *from, const char *local_field, + char **json, size_t *json_len +) { + + unsigned int retval = 1; + + if (model && oid && from && local_field) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + bson_t *pipeline = mongo_find_one_populate_array_pipeline ( + oid, from, local_field + ); + + if (pipeline) { + mongoc_cursor_t *cursor = mongoc_collection_aggregate ( + collection, 0, pipeline, NULL, NULL + ); + + if (cursor) { + const bson_t *doc = NULL; + if (mongoc_cursor_next (cursor, &doc)) { + *json = bson_as_relaxed_extended_json (doc, json_len); + } + + mongoc_cursor_destroy (cursor); + + retval = 0; + } + + bson_destroy (pipeline); + } + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } + } + + return retval; + +} + +static inline bson_t *mongo_find_one_populate_array_with_object_pipeline ( + const bson_oid_t *oid, + const char *from, const char *array_name, const char *local_field +) { + + char unwind[CMONGO_UNWIND_VALUE_SIZE] = { 0 }; + (void) snprintf ( + unwind, CMONGO_UNWIND_VALUE_SIZE - 1, + "$%s", array_name + ); + + bson_t *pipeline = BCON_NEW ( + "pipeline", + "[", + "{", + "$match", "{", "_id", BCON_OID (oid), "}", + "}", + "{", + "$unwind", BCON_UTF8 (unwind), + "}", + "{", + "$lookup", "{", + "from", BCON_UTF8 (from), + "localField", BCON_UTF8 (local_field), + "foreignField", BCON_UTF8 ("_id"), + "as", BCON_UTF8 (local_field), + "}", + "}", + "]" + ); + + return pipeline; + +} + +// works like mongo_find_one_populate_array () +// but also populates an object inside an objects array +// and converts the result into a json string +// returns 0 on success, 1 on error +unsigned int mongo_find_one_populate_array_with_object_to_json ( + const CMongoModel *model, + const bson_oid_t *oid, + const char *from, const char *array_name, const char *local_field, + char **json, size_t *json_len +) { + + unsigned int retval = 1; + + if (model && oid && from && local_field) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + bson_t *pipeline = mongo_find_one_populate_array_with_object_pipeline ( + oid, from, array_name, local_field + ); + + if (pipeline) { + mongoc_cursor_t *cursor = mongoc_collection_aggregate ( + collection, 0, pipeline, NULL, NULL + ); + + if (cursor) { + const bson_t *doc = NULL; + if (mongoc_cursor_next (cursor, &doc)) { + *json = bson_as_relaxed_extended_json (doc, json_len); + } + + mongoc_cursor_destroy (cursor); + + retval = 0; + } + + bson_destroy (pipeline); + } + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } + } + + return retval; + +} + +// returns a new string in relaxed extended JSON format +// created with the result of an aggregation that represents +// how a single object's array gets populated +// pipeline gets destroyed, opts are kept the same +// returns 0 on success, 1 on error +unsigned int mongo_find_one_custom_populate_array_to_json ( + const CMongoModel *model, + bson_t *pipeline, + char **json, size_t *json_len +) { + + unsigned int retval = 1; + + if (model && pipeline && json_len) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + mongoc_cursor_t *cursor = mongoc_collection_aggregate ( + collection, + MONGOC_QUERY_NONE, pipeline, NULL, NULL + ); + + if (cursor) { + const bson_t *doc = NULL; + if (mongoc_cursor_next (cursor, &doc)) { + *json = bson_as_relaxed_extended_json (doc, json_len); + } + + mongoc_cursor_destroy (cursor); + + retval = 0; + } + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } + + bson_destroy (pipeline); + } + + return retval; + +} + +#pragma endregion + +#pragma region insert + +// inserts a document into a collection +// destroys document +// returns 0 on success, 1 on error +unsigned int mongo_insert_one ( + const CMongoModel *model, bson_t *doc +) { + + unsigned int retval = 1; + + if (model && doc) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + #ifdef CMONGO_DEBUG + bson_error_t error = { 0 }; + + if (mongoc_collection_insert_one ( + collection, doc, NULL, NULL, &error + )) { + retval = 0; + } + + else { + (void) fprintf ( + stderr, "[MONGO][ERROR]: %s\n", error.message + ); + } + #else + if (mongoc_collection_insert_one ( + collection, doc, NULL, NULL, NULL + )) { + retval = 0; + } + #endif + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } + + bson_destroy (doc); + } + + return retval; + +} + +// inserts many documents into a collection +// docs are NOT deleted after the operation +// returns 0 on success, 1 on error +unsigned int mongo_insert_many ( + const CMongoModel *model, + const bson_t **docs, size_t n_docs +) { + + unsigned int retval = 1; + + if (model && docs) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + #ifdef CMONGO_DEBUG + bson_error_t error = { 0 }; + + if (mongoc_collection_insert_many ( + collection, docs, n_docs, NULL, NULL, &error + )) { + retval = 0; + } + + else { + (void) fprintf ( + stderr, "[MONGO][ERROR]: %s\n", error.message + ); + } + #else + if (mongoc_collection_insert_many ( + collection, docs, n_docs, NULL, NULL, NULL + )) { + retval = 0; + } + #endif + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } + } + + return retval; + +} + +#pragma endregion + +#pragma region update + +// updates a doc by a matching query with the new values +// destroys query and update documents +// returns 0 on success, 1 on error +unsigned int mongo_update_one ( + const CMongoModel *model, + bson_t *query, bson_t *update +) { + + unsigned int retval = 1; + + if (model && query && update) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + #ifdef CMONGO_DEBUG + bson_error_t error = { 0 }; + + if (mongoc_collection_update_one ( + collection, query, update, NULL, NULL, &error + )) { + retval = 0; + } + + else { + (void) fprintf ( + stderr, "[MONGO][ERROR]: %s\n", error.message + ); + } + #else + if (mongoc_collection_update_one ( + collection, query, update, NULL, NULL, NULL + )) { + retval = 0; + } + #endif + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } + + bson_destroy (query); + bson_destroy (update); + } + + return retval; + +} + +// updates all the query matching documents +// destroys the query and the update documents +// returns 0 on success, 1 on error +unsigned int mongo_update_many ( + const CMongoModel *model, + bson_t *query, bson_t *update +) { + + unsigned int retval = 0; + + if (model && query && update) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + #ifdef CMONGO_DEBUG + bson_error_t error = { 0 }; + + if (mongoc_collection_update_many ( + collection, query, update, NULL, NULL, &error + )) { + retval = 0; + } + + else { + (void) fprintf ( + stderr, "[MONGO][ERROR]: %s\n", error.message + ); + } + #else + if (mongoc_collection_update_many ( + collection, query, update, NULL, NULL, NULL + )) { + retval = 0; + } + #endif + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } + + bson_destroy (query); + bson_destroy (update); + } + + return retval; + +} + +#pragma endregion + +#pragma region delete + +// deletes one matching document by a query +// destroys the query document +// returns 0 on success, 1 on error +unsigned int mongo_delete_one ( + const CMongoModel *model, bson_t *query +) { + + unsigned int retval = 0; + + if (model && query) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + #ifdef CMONGO_DEBUG + bson_error_t error = { 0 }; + + if (mongoc_collection_delete_one ( + collection, query, NULL, NULL, &error + )) { + retval = 0; + } + + else { + (void) fprintf ( + stderr, "[MONGO][ERROR]: %s\n", error.message + ); + } + #else + if (mongoc_collection_delete_one ( + collection, query, NULL, NULL, NULL + )) { + retval = 0; + } + #endif + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } + + bson_destroy (query); + } + + return retval; + +} + +// deletes all the query matching documents +// destroys the query +// returns 0 on success, 1 on error +unsigned int mongo_delete_many ( + const CMongoModel *model, bson_t *query +) { + + unsigned int retval = 0; + + if (model && query) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + #ifdef CMONGO_DEBUG + bson_error_t error = { 0 }; + + if (mongoc_collection_delete_many ( + collection, query, NULL, NULL, &error + )) { + retval = 0; + } + + else { + (void) fprintf ( + stderr, "[MONGO][ERROR]: %s\n", error.message + ); + } + #else + if (mongoc_collection_delete_many ( + collection, query, NULL, NULL, NULL + )) { + retval = 0; + } + #endif + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } + + bson_destroy (query); + } + + return retval; + +} + +#pragma endregion + +#pragma region aggregation + +// performs an aggregation in the model's collection +// the pipeline document gets destroyed +// returns a cursor with the aggregation's result +mongoc_cursor_t *mongo_perform_aggregation_with_opts ( + const CMongoModel *model, + mongoc_query_flags_t flags, + const bson_t *opts, + bson_t *pipeline +) { + + mongoc_cursor_t *cursor = NULL; + + if (model && pipeline) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + cursor = mongoc_collection_aggregate ( + collection, + flags, pipeline, opts, NULL + ); + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } + + bson_destroy (pipeline); + } + + return cursor; + +} + +// works like mongo_perform_aggregation_with_opts () +// but sets flags to 0 and opts to NULL +mongoc_cursor_t *mongo_perform_aggregation ( + const CMongoModel *model, bson_t *pipeline +) { + + return mongo_perform_aggregation_with_opts ( + model, 0, NULL, pipeline + ); + +} + +// finds one document by matching id with custom pipeline +// usefull when trying to perform custom populate methods +// returns 0 on success, 1 on error +unsigned int mongo_perform_single_aggregation ( + const CMongoModel *model, bson_t *pipeline, void *output +) { + + unsigned int retval = 1; + + if (model && pipeline) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + retval = mongo_find_one_aggregate_internal ( + collection, pipeline, + output, model->model_parser + ); + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } + + bson_destroy (pipeline); + } + + return retval; + +} + +// works like mongo_perform_single_aggregation_to_json () +// but outputs the result directly into a json string +// returns 0 on success, 1 on error +unsigned int mongo_perform_single_aggregation_to_json ( + const CMongoModel *model, bson_t *pipeline, + char **json, size_t *json_len +) { + + unsigned int retval = 1; + + if (model && pipeline) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + mongoc_cursor_t *cursor = mongoc_collection_aggregate ( + collection, + MONGOC_QUERY_NONE, pipeline, NULL, NULL + ); + + if (cursor) { + const bson_t *doc = NULL; + if (mongoc_cursor_next (cursor, &doc)) { + *json = bson_as_relaxed_extended_json (doc, json_len); + } + + mongoc_cursor_destroy (cursor); + + retval = 0; + } + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } + + bson_destroy (pipeline); + } + + return retval; + +} + +// works like mongo_perform_aggregation () +// but outputs all the aggregation's result into an array +// useful when aggregation returns many matches +// returns 0 on success, 1 on error +unsigned int mongo_perform_aggregation_to_json ( + const CMongoModel *model, bson_t *pipeline, + const char *array_name, + char **json, size_t *json_len +) { + + unsigned int retval = 1; + + if (model && pipeline) { + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + if (client) { + mongoc_collection_t *collection = mongoc_client_get_collection ( + client, mongo.db_name, model->collname + ); + + if (collection) { + mongoc_cursor_t *cursor = mongoc_collection_aggregate ( + collection, + MONGOC_QUERY_NONE, pipeline, NULL, NULL + ); + + if (cursor) { + *json = mongo_find_all_to_json_internal ( + cursor, array_name, json_len + ); + + mongoc_cursor_destroy (cursor); + + retval = 0; + } + + mongoc_collection_destroy (collection); + } + + mongoc_client_pool_push (mongo.pool, client); + } + + bson_destroy (pipeline); + } + + return retval; + +} + +#pragma endregion + +#ifdef __cplusplus #pragma GCC diagnostic pop -#endif \ No newline at end of file +#endif diff --git a/src/cmongo/model.c b/src/cmongo/model.c new file mode 100644 index 0000000..0121913 --- /dev/null +++ b/src/cmongo/model.c @@ -0,0 +1,43 @@ +#include +#include + +#include "cmongo/model.h" + +CMongoModel *cmongo_model_new (void) { + + CMongoModel *model = (CMongoModel *) malloc (sizeof (CMongoModel)); + if (model) { + (void) memset (model, 0, sizeof (CMongoModel)); + } + + return model; + +} + +void cmongo_model_delete (void *model_ptr) { + + if (model_ptr) free (model_ptr); + +} + +CMongoModel *cmongo_model_create (const char *collname) { + + CMongoModel *model = cmongo_model_new (); + if (model && collname) { + (void) strncpy (model->collname, collname, CMONGO_COLLNAME_SIZE - 1); + model->collname_len = strlen (model->collname); + } + + return model; + +} + +void cmongo_model_set_parser ( + CMongoModel *model, const mongo_parser model_parser +) { + + if (model) { + model->model_parser = model_parser; + } + +} \ No newline at end of file diff --git a/src/cmongo/mongo.c b/src/cmongo/mongo.c index 68e3972..1e6ecec 100644 --- a/src/cmongo/mongo.c +++ b/src/cmongo/mongo.c @@ -9,148 +9,166 @@ #include "cmongo/mongo.h" -static MongoStatus status = MONGO_STATUS_DISCONNECTED; +Mongo mongo = { 0 }; -MongoStatus mongo_get_status (void) { return status; } +MongoStatus mongo_get_status (void) { return mongo.status; } -static mongoc_uri_t *uri = NULL; -mongoc_client_t *client = NULL; -static mongoc_database_t *database = NULL; +void mongo_set_db_name (const char *db_name) { -static char *host = NULL; + if (db_name) { + (void) strncpy (mongo.db_name, db_name, CMONGO_DB_NAME_SIZE - 1); + mongo.db_name_len = strlen (mongo.db_name); + } -void mongo_set_host (const char *h) { - - if (h) host = strdup (h); - } -static char *port = NULL; +void mongo_set_host (const char *host) { + + if (host) { + (void) strncpy (mongo.host, host, CMONGO_HOST_SIZE - 1); + mongo.host_len = strlen (mongo.host); + } -void mongo_set_port (const char *p) { - - if (p) port = strdup (p); - } -static char *username = NULL; +void mongo_set_port (const unsigned int port) { + + mongo.port = port; -void mongo_set_username (const char *u) { - - if (u) username = strdup (u); - } -static char *password = NULL; +void mongo_set_username (const char *username) { -void mongo_set_password (const char *pswd) { - - if (pswd) password = strdup (pswd); - -} + if (username) { + (void) strncpy (mongo.username, username, CMONGO_USERNAME_SIZE - 1); + mongo.username_len = strlen (mongo.username); + } -char *db_name = NULL; +} -void mongo_set_db_name (const char *name) { +void mongo_set_password (const char *pswd) { - if (name) db_name = strdup (name); + if (pswd) { + (void) strncpy (mongo.password, pswd, CMONGO_PASSWORD_SIZE - 1); + mongo.password_len = strlen (mongo.password); + } } -static char *app_name = NULL; +void mongo_set_app_name (const char *app_name) { -void mongo_set_app_name (const char *name) { - - if (name) app_name = strdup (name); + if (app_name) { + (void) strncpy (mongo.app_name, app_name, CMONGO_APP_NAME_SIZE - 1); + mongo.app_name_len = strlen (mongo.app_name); + } } -static char *uri_string = NULL; - void mongo_set_uri (const char *uri) { - if (uri) uri_string = strdup (uri); + if (uri) { + (void) strncpy (mongo.uri, uri, CMONGO_URI_SIZE - 1); + mongo.uri_len = strlen (mongo.uri); + } } -// generates a new uri string with the set values (username, password, host, port & db name) +// generates a new uri string with the set values +// (username, password, host, port & db name) // that can be used to set as the uri for a new connection -// returns the newly uri string (that should be freed) on success, NULL on error -char *mongo_uri_generate (void) { - - char *retval = NULL; - - if (host && port && db_name) { - char buffer[512] = { 0 }; - if (username && password) { - (void) snprintf ( - buffer, 512, - "mongodb://%s:%s@%s:%s/%s", - username, password, - host, port, - db_name +// returns 0 on success 1 on error +unsigned int mongo_uri_generate (void) { + + unsigned int retval = 1; + + if ( + mongo.host_len + && mongo.port + && mongo.db_name_len + ) { + if (mongo.username_len && mongo.password_len) { + mongo.uri_len = snprintf ( + mongo.uri, CMONGO_URI_SIZE - 1, + "mongodb://%s:%s@%s:%u/%s", + mongo.username, mongo.password, + mongo.host, mongo.port, + mongo.db_name ); + + retval = 0; } else { - (void) snprintf ( - buffer, 512, - "mongodb://%s:%s/%s", - host, port, - db_name + mongo.uri_len = snprintf ( + mongo.uri, CMONGO_URI_SIZE - 1, + "mongodb://%s:%u/%s", + mongo.host, mongo.port, + mongo.db_name ); + + retval = 0; } + } + + return retval; + +} + +static unsigned int mongo_connect_internal ( + const mongoc_uri_t *uri +) { + + unsigned int retval = 1; + + mongo.pool = mongoc_client_pool_new (uri); + if (mongo.pool) { + mongoc_client_pool_set_error_api (mongo.pool, 2); - retval = strdup (buffer); + if (mongo.app_name_len) { + mongoc_client_pool_set_appname ( + mongo.pool, mongo.app_name + ); + } + + mongo.status = MONGO_STATUS_CONNECTED; + + retval = 0; } return retval; } -// pings the db to test for a success connection -// Possible connection problems -- failed to authenticate to the db -// returns 0 on success, 1 on error -int mongo_ping_db (void) { - - int retval = 1; - - if (client) { - if (db_name) { - bson_t *command = NULL, reply = { 0 }; - bson_error_t error = { 0 }; - - command = BCON_NEW ("ping", BCON_INT32 (1)); - if (mongoc_client_command_simple ( - client, - db_name, - command, - NULL, - &reply, - &error - )) { - // success - char *str = bson_as_json (&reply, NULL); - if (str) { - (void) fprintf (stdout, "\n%s\n", str); - free (str); - } - - retval = 0; - } +// connect to the mongo db with db name +unsigned int mongo_connect (void) { - else { - (void) fprintf ( - stderr, "[MONGO][ERROR]: %s\n", error.message - ); - } + unsigned int retval = 1; + + if (mongo.uri_len) { + bson_error_t error = { 0 }; + + mongoc_init (); // init mongo internals + + // safely create mongo uri object + mongoc_uri_t *uri = mongoc_uri_new_with_error ( + mongo.uri, &error + ); + + if (uri) { + retval = mongo_connect_internal (uri); + + mongoc_uri_destroy (uri); } else { (void) fprintf ( stderr, - "[MONGO][ERROR]: DB name hasn't been set! " - "Use mongo_set_db_name ()\n" + "Failed to parse Mongo URI: %s", + mongo.uri + ); + + (void) fprintf ( + stderr, "Error: %s\n\n", error.message ); } } @@ -158,8 +176,8 @@ int mongo_ping_db (void) { else { (void) fprintf ( stderr, - "[MONGO][ERROR]: Not connected to mongo! " - "Call mongo_connect () first\n" + "Not uri string! " + "Call mongo_set_uri () before attempting a connection\n" ); } @@ -167,55 +185,46 @@ int mongo_ping_db (void) { } -// connect to the mongo db with db name -int mongo_connect (void) { +// pings the db to test for a success connection +// returns 0 on success, 1 on error +unsigned int mongo_ping_db (void) { - int retval = 1; + unsigned int retval = 1; - if (uri_string) { + if (mongo.status == MONGO_STATUS_CONNECTED) { + bson_t *command = NULL, reply = { 0 }; bson_error_t error = { 0 }; - mongoc_init (); // init mongo internals - - // safely create mongo uri object - uri = mongoc_uri_new_with_error (uri_string, &error); - if (uri) { - // create a new client instance - client = mongoc_client_new_from_uri (uri); - if (client) { - // register the app name -> for logging info - mongoc_client_set_appname (client, app_name); - - status = MONGO_STATUS_CONNECTED; - - retval = 0; + // get a client from the pool + mongoc_client_t *client = mongoc_client_pool_pop (mongo.pool); + + command = BCON_NEW ("ping", BCON_INT32 (1)); + + if (mongoc_client_command_simple ( + client, + mongo.db_name, + command, + NULL, + &reply, + &error + )) { + // success + char *str = bson_as_json (&reply, NULL); + if (str) { + (void) fprintf (stdout, "\n%s\n", str); + free (str); } - else { - (void) fprintf ( - stderr, "Failed to create a new client instance!\n" - ); - } + retval = 0; } else { (void) fprintf ( - stderr, - "failed to parse URI: %s\n" - "error message: %s\n", - uri_string, - error.message + stderr, "[MONGO][ERROR]: %s\n", error.message ); } } - else { - (void) fprintf ( - stderr, "Not uri string! " - "Call mongo_set_uri () before attemting a connection\n" - ); - } - return retval; } @@ -223,21 +232,11 @@ int mongo_connect (void) { // disconnects from the db void mongo_disconnect (void) { - mongoc_database_destroy (database); - mongoc_uri_destroy (uri); - mongoc_client_destroy (client); - - if (host) free (host); - if (port) free (port); - if (username) free (username); - if (password) free (password); - - if (app_name) free (app_name); - if (uri_string) free (uri_string); - if (db_name) free (db_name); + mongoc_client_pool_destroy (mongo.pool); + mongo.pool = NULL; mongoc_cleanup (); - status = MONGO_STATUS_DISCONNECTED; + mongo.status = MONGO_STATUS_DISCONNECTED; } \ No newline at end of file diff --git a/src/cmongo/select.c b/src/cmongo/select.c index 1094a12..fdc8036 100644 --- a/src/cmongo/select.c +++ b/src/cmongo/select.c @@ -4,159 +4,41 @@ #include "cmongo/select.h" -#pragma region internal - -static CMongoSelectField *cmongo_select_field_new (void) { - - CMongoSelectField *field = (CMongoSelectField *) malloc (sizeof (CMongoSelectField)); - if (field) { - field->prev = NULL; - - (void) memset (field->value, 0, CMONGO_SELECT_FIELD_LEN); - field->len = 0; - - field->next = NULL; - } - - return field; - -} - -static void cmongo_select_field_delete (CMongoSelectField *field) { - - if (field) free (field); - -} - -static void cmongo_select_internal_insert_after ( - CMongoSelect *select, - CMongoSelectField *element, - CMongoSelectField *field -) { - - if (element == NULL) { - if (select->size == 0) select->end = field; - else select->start->prev = field; - - field->next = select->start; - field->prev = NULL; - select->start = field; - } - - else { - if (element->next == NULL) select->end = field; - - field->next = element->next; - field->prev = element; - element->next = field; - } - - select->size++; - -} - -static CMongoSelectField *cmongo_select_internal_remove_element ( - CMongoSelect *select, - CMongoSelectField *element -) { - - CMongoSelectField *old = NULL; - - if (element == NULL) { - old = select->start; - select->start = select->start->next; - if (select->start != NULL) select->start->prev = NULL; - } - - else { - old = element; - - CMongoSelectField *prevElement = element->prev; - CMongoSelectField *nextElement = element->next; - - if (prevElement != NULL && nextElement != NULL) { - prevElement->next = nextElement; - nextElement->prev = prevElement; - } - - else { - // we are at the start of the select - if (prevElement == NULL) { - if (nextElement != NULL) nextElement->prev = NULL; - select->start = nextElement; - } - - // we are at the end of the select - if (nextElement == NULL) { - if (prevElement != NULL) prevElement->next = NULL; - select->end = prevElement; - } - } - } - - return old; - -} - -#pragma endregion - -#pragma region public - -CMongoSelectField *cmongo_select_field_create ( - const char *value -) { - - CMongoSelectField *field = cmongo_select_field_new (); - if (field && value) { - (void) strncpy (field->value, value, CMONGO_SELECT_FIELD_LEN - 1); - field->len = strlen (field->value); - } - - return field; - -} - CMongoSelect *cmongo_select_new (void) { CMongoSelect *select = (CMongoSelect *) malloc (sizeof (CMongoSelect)); if (select) { - select->size = 0; - - select->start = NULL; - select->end = NULL; + (void) memset (select, 0, sizeof (CMongoSelect)); } return select; } -bool cmongo_select_is_empty (const CMongoSelect *select) { - - return select ? (select->size == 0) : false; - -} - -bool cmongo_select_is_not_empty (const CMongoSelect *select) { - - return select ? (select->size > 0) : false; - -} - // adds a new field to the select list // returns 0 on success, 1 on error int cmongo_select_insert_field ( - CMongoSelect *select, - CMongoSelectField *field + CMongoSelect *select, const char *field ) { int retval = 1; if (select && field) { - cmongo_select_internal_insert_after ( - select, select->end, field - ); + if (select->n_fields < CMONGO_SELECT_FIELDS_SIZE) { + (void) strncpy ( + select->fields[select->n_fields].value, + field, + CMONGO_SELECT_FIELD_LEN - 1 + ); + + select->fields[select->n_fields].len = strlen ( + select->fields[select->n_fields].value + ); - retval = 0; + select->n_fields += 1; + + retval = 0; + } } return retval; @@ -166,24 +48,7 @@ int cmongo_select_insert_field ( void cmongo_select_delete (void *cmongo_select_ptr) { if (cmongo_select_ptr) { - CMongoSelect *select = (CMongoSelect *) cmongo_select_ptr; - - while (select->size > 0) { - cmongo_select_field_delete ( - cmongo_select_internal_remove_element ( - select, NULL - ) - ); - - select->size--; - - if (select->size == 0) { - select->start = NULL; - select->end = NULL; - } - } - - free (select); + free (cmongo_select_ptr); } } diff --git a/src/cmongo/types.c b/src/cmongo/types.c index 857a20a..466558a 100644 --- a/src/cmongo/types.c +++ b/src/cmongo/types.c @@ -1,8 +1,11 @@ #include +#include #include #include +#include "cmongo/types.h" + bson_oid_t *bson_oid_new (void) { bson_oid_t *oid = (bson_oid_t *) malloc (sizeof (bson_oid_t)); @@ -26,4 +29,23 @@ bson_oid_t *bson_oid_create (const bson_oid_t *original_oid) { return oid; +} + +void bson_oid_print (const bson_oid_t *oid) { + + char buffer[BSON_OID_BUFFER_SIZE] = { 0 }; + bson_oid_to_string (oid, buffer); + (void) printf ("%s\n", buffer); + +} + +void bson_print_as_json (const bson_t *document) { + + size_t json_len = 0; + char *json = bson_as_relaxed_extended_json (document, &json_len); + if (json) { + (void) printf ("%s\n", json); + free (json); + } + } \ No newline at end of file diff --git a/test/model.c b/test/model.c new file mode 100644 index 0000000..1181995 --- /dev/null +++ b/test/model.c @@ -0,0 +1,76 @@ +#include +#include +#include + +#include + +#include "test.h" + +static const char *collname = { "users" }; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" + +static void test_model_parser ( + void *model, const bson_t *doc +) { + + // nothing to be done here + +} + +#pragma GCC diagnostic pop + +static void test_model_new_single (void) { + + CMongoModel *model = cmongo_model_new (); + + test_check_unsigned_eq (model->collname_len, 0, NULL); + test_check_null_ptr (model->model_parser); + + cmongo_model_delete (model); + +} + +static void test_model_create_single (void) { + + CMongoModel *model = cmongo_model_create (collname); + + test_check_unsigned_eq (model->collname_len, strlen (collname), NULL); + test_check_str_eq (model->collname, collname, NULL); + test_check_null_ptr (model->model_parser); + + cmongo_model_delete (model); + +} + +static void test_model_set_parser (void) { + + CMongoModel *model = cmongo_model_create (collname); + + test_check_unsigned_eq (model->collname_len, strlen (collname), NULL); + test_check_str_eq (model->collname, collname, NULL); + test_check_null_ptr (model->model_parser); + + cmongo_model_set_parser (model, test_model_parser); + test_check_ptr (model->model_parser); + + cmongo_model_delete (model); + +} + +int main (int argc, char **argv) { + + (void) printf ("Testing MODEL...\n"); + + test_model_new_single (); + + test_model_create_single (); + + test_model_set_parser (); + + (void) printf ("\nDone with MODEL tests!\n\n"); + + return 0; + +} \ No newline at end of file diff --git a/test/run.sh b/test/run.sh new file mode 100644 index 0000000..9f37620 --- /dev/null +++ b/test/run.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +./test/bin/model || { exit 1; } + +./test/bin/select || { exit 1; } diff --git a/test/select.c b/test/select.c new file mode 100644 index 0000000..8162489 --- /dev/null +++ b/test/select.c @@ -0,0 +1,67 @@ +#include +#include +#include + +#include + +#include "test.h" + +static void test_select_create_single (void) { + + CMongoSelect *select = cmongo_select_new (); + + test_check_ptr (select); + test_check_int_eq (select->n_fields, 0, NULL); + + cmongo_select_insert_field (select, "name"); + + test_check_int_eq (select->n_fields, 1, NULL); + test_check_str_eq (select->fields[0].value, "name", NULL); + test_check_str_len (select->fields[0].value, strlen ("name"), NULL); + + cmongo_select_delete (select); + +} + +static void test_select_create_multiple (void) { + + CMongoSelect *select = cmongo_select_new (); + + test_check_ptr (select); + test_check_int_eq (select->n_fields, 0, NULL); + + cmongo_select_insert_field (select, "name"); + + test_check_int_eq (select->n_fields, 1, NULL); + test_check_str_eq (select->fields[0].value, "name", NULL); + test_check_str_len (select->fields[0].value, strlen ("name"), NULL); + + cmongo_select_insert_field (select, "email"); + + test_check_int_eq (select->n_fields, 2, NULL); + test_check_str_eq (select->fields[1].value, "email", NULL); + test_check_str_len (select->fields[1].value, strlen ("email"), NULL); + + cmongo_select_insert_field (select, "username"); + + test_check_int_eq (select->n_fields, 3, NULL); + test_check_str_eq (select->fields[2].value, "username", NULL); + test_check_str_len (select->fields[2].value, strlen ("username"), NULL); + + cmongo_select_delete (select); + +} + +int main (int argc, char **argv) { + + (void) printf ("Testing SELECT...\n"); + + test_select_create_single (); + + test_select_create_multiple (); + + (void) printf ("\nDone with SELECT tests!\n\n"); + + return 0; + +} \ No newline at end of file diff --git a/test/test.h b/test/test.h new file mode 100644 index 0000000..374e37b --- /dev/null +++ b/test/test.h @@ -0,0 +1,176 @@ +#ifndef _CMONGO_TESTS_H_ +#define _CMONGO_TESTS_H_ + +#include +#include + +#define where (void) fprintf (stderr, "%s:%d -> ", __FILE__, __LINE__) + +#define \ + fail(msg) \ + ({ \ + where; \ + (void) fprintf (stderr, "%s\n", msg); \ + exit (1); \ + }) + +#define \ + test_check(result, msg) \ + ({ \ + if ((result) == false) { \ + where; \ + (void) fprintf ( \ + stderr, \ + "Check has failed!\n" \ + ); \ + \ + if (msg) (void) fprintf (stderr, "%s\n", (char *) msg); \ + exit (1); \ + } \ + }) + +#define test_check_ptr_eq(X, Y) test_check (X == Y, NULL) + +#define test_check_ptr_ne(X, Y) test_check (X != Y, NULL) + +#define test_check_int_ne(X, Y) test_check (X != Y, NULL) + +#define test_check_int_gt(X, Y) test_check (X > Y, NULL) + +#define \ + test_check_ptr(ptr) \ + ({ \ + if (!ptr) { \ + where; \ + (void) fprintf (stderr, "Pointer is NULL!"); \ + exit (1); \ + } \ + }) + +#define \ + test_check_null_ptr(ptr) \ + ({ \ + if (ptr) { \ + where; \ + (void) fprintf (stderr, "Pointer is NOT NULL!"); \ + exit (1); \ + } \ + }) + +#define \ + test_check_true(value) \ + ({ \ + if (!value) { \ + where; \ + (void) fprintf (stderr, "Value is NOT true!"); \ + exit (1); \ + } \ + }) + +#define \ + test_check_false(value) \ + ({ \ + if (value) { \ + where; \ + (void) fprintf (stderr, "Value is NOT false!"); \ + exit (1); \ + } \ + }) + +#define \ + test_check_bool_eq(value, expected, msg) \ + ({ \ + if (value != expected) { \ + where; \ + (void) fprintf ( \ + stderr, \ + "BOOL %d does not match %d\n", \ + value, expected \ + ); \ + \ + if (msg) (void) fprintf (stderr, "%s\n", (char *) msg); \ + exit (1); \ + } \ + }) + +#define \ + test_check_unsigned_eq(value, expected, msg) \ + ({ \ + if (value != expected) { \ + where; \ + (void) fprintf ( \ + stderr, \ + "UNSIGNED %d does not match %d\n", \ + value, expected \ + ); \ + \ + if (msg) (void) fprintf (stderr, "%s\n", (char *) msg); \ + exit (1); \ + } \ + }) + +#define \ + test_check_int_eq(value, expected, msg) \ + ({ \ + if (value != expected) { \ + where; \ + (void) fprintf ( \ + stderr, \ + "INTEGER %d does not match %d\n", \ + value, expected \ + ); \ + \ + if (msg) (void) fprintf (stderr, "%s\n", (char *) msg); \ + exit (1); \ + } \ + }) + +#define \ + test_check_long_int_eq(value, expected, msg) \ + ({ \ + if (value != expected) { \ + where; \ + (void) fprintf ( \ + stderr, \ + "INTEGER %ld does not match %ld\n", \ + value, expected \ + ); \ + \ + if (msg) (void) fprintf (stderr, "%s\n", (char *) msg); \ + exit (1); \ + } \ + }) + +#define \ + test_check_str_eq(value, expected, msg) \ + ({ \ + if (strcmp ((char *) value, (char *) expected)) { \ + where; \ + (void) fprintf ( \ + stderr, \ + "STRING %s does not match %s\n", \ + (char *) value, (char *) expected \ + ); \ + \ + if (msg) (void) fprintf (stderr, "%s\n", (char *) msg); \ + exit (1); \ + } \ + }) + +#define \ + test_check_str_len(string, expected, msg) \ + ({ \ + if (strlen ((char *) string) != expected) { \ + where; \ + (void) fprintf ( \ + stderr, \ + "STRING %s LEN does not match %d\n", \ + (char *) string, expected \ + ); \ + \ + if (msg) (void) fprintf (stderr, "%s\n", (char *) msg); \ + exit (1); \ + } \ + }) + +#endif \ No newline at end of file diff --git a/test/version.c b/test/version.c new file mode 100644 index 0000000..3b769b9 --- /dev/null +++ b/test/version.c @@ -0,0 +1,60 @@ +#include +#include +#include + +#include + +#include + +// opens and reads a file into a buffer +// sets file size to the amount of bytes read +static char *file_read (const char *filename, size_t *file_size) { + + char *file_contents = NULL; + + if (filename) { + struct stat filestatus = { 0 }; + if (!stat (filename, &filestatus)) { + FILE *fp = fopen (filename, "rt"); + if (fp) { + *file_size = filestatus.st_size; + file_contents = (char *) malloc (filestatus.st_size); + + // read the entire file into the buffer + if (fread (file_contents, filestatus.st_size, 1, fp) != 1) { + (void) fprintf ( + stderr, "Failed to read file (%s) contents!", filename + ); + + free (file_contents); + } + + (void) fclose (fp); + } + } + + + else { + (void) fprintf (stderr, "Unable to open file %s.", filename); + } + } + + return file_contents; + +} + +int main (int argc, char **argv) { + + // get version from file + size_t version_len = 0; + char *version_from_file = file_read ("version.txt", &version_len); + + if (version_from_file) { + if (!strcmp (CMONGO_VERSION, version_from_file)) { + return 0; + } + } + + return 1; + +} \ No newline at end of file diff --git a/version.txt b/version.txt new file mode 100644 index 0000000..394835b --- /dev/null +++ b/version.txt @@ -0,0 +1 @@ +1.0b-13 \ No newline at end of file