{"id":13435023,"url":"https://github.com/ErlyORM/boss_db","last_synced_at":"2025-03-18T02:30:45.889Z","repository":{"id":2388531,"uuid":"3354440","full_name":"ErlyORM/boss_db","owner":"ErlyORM","description":"BossDB: a sharded, caching, pooling, evented ORM for Erlang","archived":false,"fork":false,"pushed_at":"2024-01-05T12:27:11.000Z","size":3063,"stargazers_count":277,"open_issues_count":61,"forks_count":137,"subscribers_count":31,"default_branch":"master","last_synced_at":"2024-10-27T17:24:59.858Z","etag":null,"topics":["erlang","orm"],"latest_commit_sha":null,"homepage":"","language":"Erlang","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ErlyORM.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2012-02-04T18:32:42.000Z","updated_at":"2024-09-05T08:21:23.000Z","dependencies_parsed_at":"2022-07-21T04:39:02.762Z","dependency_job_id":"eb48d33f-7128-4703-96a2-2790eba680bb","html_url":"https://github.com/ErlyORM/boss_db","commit_stats":{"total_commits":400,"total_committers":55,"mean_commits":"7.2727272727272725","dds":0.86,"last_synced_commit":"40aad5dea36fbada2c9e99f73d8bd8180c01cf30"},"previous_names":[],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ErlyORM%2Fboss_db","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ErlyORM%2Fboss_db/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ErlyORM%2Fboss_db/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ErlyORM%2Fboss_db/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ErlyORM","download_url":"https://codeload.github.com/ErlyORM/boss_db/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244143878,"owners_count":20405294,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["erlang","orm"],"created_at":"2024-07-31T03:00:30.708Z","updated_at":"2025-03-18T02:30:45.578Z","avatar_url":"https://github.com/ErlyORM.png","language":"Erlang","readme":"BossDB: A sharded, caching, pooling, evented ORM for Erlang\n===========================================================\n[![Build Status](https://travis-ci.org/ErlyORM/boss_db.svg?branch=master)](https://travis-ci.org/ErlyORM/boss_db)\n\nAttention! This is a master branch supporting Erlang 18 and above. For older Erlang versions use legacy branch.\n\nSupported databases\n-------------------\n\n* *NEW* DynamoDB (experimental)\n* Mnesia\n* MongoDB\n* MySQL\n* PostgreSQL\n* Riak\n* Tokyo Tyrant\n\nComplete API references\n-----------------------\n\nQuerying: http://www.chicagoboss.org/doc/api-db.html\n\nRecords: http://www.chicagoboss.org/doc/api-record.html\n\nBossNews: http://chicagoboss.org/doc/api-news.html\n\nWrite an adapter: https://github.com/ChicagoBoss/ChicagoBoss/wiki/DB-Adapter-Quickstart\n\nUsage\n-----\n```erlang\nboss_db:start(DBOptions),\nboss_cache:start(CacheOptions), % If you want cacheing with Memcached\nboss_news:start() % Mandatory! Hopefully will be optional one day\n\nDBOptions = [\n    {adapter, mock | tyrant | riak | mysql | pgsql | mnesia | mongodb},\n    {db_host, HostName::string()},\n    {db_port, PortNumber::integer()},\n    {db_username, UserName::string()},\n    {db_password, Password::string()},\n    {db_database, Database::string()},\n    {db_configure, DatabaseOptions::list()},\n    {db_ssl, UseSSL::boolean() | required}, % for now pgsql only\n\n    {shards, [\n        {db_shard_models, [ModelName::atom()]},\n        {db_shard_id, ShardId::atom()},\n        {db_host, _}, {db_port, _}, ...\n    ]},\n    {cache_enable, true | false},\n    {cache_exp_time, TTLSeconds::integer()},\n\n    {size, PoolSize::non_neg_integer()}, % the size of the connection pool - defaults to 5\n    {max_overflow, MaxOverflow::non_neg_integer()} % the maximum number of temporary extra workers that can be created past the `size' just above - defaults to 10\n    %% the sum size + max_overflow effectively controls how many concurrent mysql queries can run\n]\n\nCacheOptions = [\n    {adapter, memcached_bin | redis | ets},\n    {cache_servers, MemcachedCacheServerOpts | RedisCacheServerOpts | EtsCacheServerOpts}\n]\n\nMemcachedCacheServerOpts = [\n    { HostName::string() = \"localhost\"\n    , Port::integer()    = 11211\n    , Weight::integer()  = 1\n    }, ...\n]\n\nRedisCacheServerOpts = [\n    {host,      HostName::string()   = \"localhost\"},\n    {port,      Port::integer()      = 6379},\n    {pass,      Password::string()   = undefined},\n    {db,        Db::integer()        = 0},\n    {reconnect, Reconnect::boolean() = true}\n]\n\nEtsCacheServerOpts = [\n    {ets_maxsize,   MaxSize::integer() = 32 * 1024 * 1024},\n    {ets_threshold, Threshold::float() = 0.85},\n    {ets_weight,    Weight::integer()  = 30}\n]\n```\n\nIntroduction\n------------\n\nBossDB is a compiler chain and run-time library for accessing a database via\nErlang parameterized modules. It solves the age-old problem of retrieving\nnamed fields without resorting to verbosities like proplists:get_value/2 or\ndict:find/2. For example, if you want to look up a puppy by ID and print its\nname, you would write:\n\n```erlang\nPuppy = boss_db:find(\"puppy-1\"),\nio:format(\"Puppy's name: ~p~n\", [Puppy:name()]).\n```\n\nFunctions for accessing field names are generated automatically. All you need\nto do is create a model file and compile it with boss_record_compiler. Example:\n\nThe model file, call it puppy.erl:\n\n```erlang\n-module(puppy, [Id, Name, BreedId]).\n```\n\nThen compile it like:\n\n```erlang\n{ok, puppy} = boss_record_compiler:compile(\"puppy.erl\")\n```\n\n...and you're ready to go.\n\nYou can also enable boss_db_rebar plugin in your rebar.config to automatize\ncompilation:\n\n```erlang\n{plugin_dir, [\"deps/boss_db/priv/rebar\"]}.\n{plugins, [boss_db_rebar]}.\n{boss_db_opts, [\n    {model_dir, \"src/model\"}\n]}.\n```\n\nAssociations\n------------\n\nBossDB supports database associations. Suppose you want to model the dog breed\n(golden retriever, poodle, etc). You would create a model file with a special\n\"-has\" attribute, like:\n\n```erlang\n-module(breed, [Id, Name]).\n-has({puppies, many}).\n```\nThen back in puppy.erl you'd add a \"-belongs_to\" attribute:\n\n```erlang\n-module(puppy, [Id, Name, BreedId]).\n-belongs_to(breed).\n```\n\nOnce you've compiled breed.erl with boss_record_compiler, you can print a puppy's\nassociated breed like:\n\n```erlang\nBreed = Puppy:breed(),\nio:format(\"Puppy's breed: ~p~n\", [Breed:name()]).\n```\n\nSimilarly, you could iterate over all the puppies of a particular breed:\n\n```erlang\nBreed = boss_db:find(\"breed-47\"),\nlists:map(fun(Puppy) -\u003e \n        io:format(\"Puppy: ~p~n\", [Puppy:name()]) \n    end, Breed:puppies())\n```\n\nQuerying\n--------\n\nYou can search the database with the boss_db:find functions. Example:\n\n```erlang\nPuppies = boss_db:find(puppy, [{breed_id, 'equals', \"breed-47\"}])\n```\n\nThis is somewhat verbose. If you compile the source file with boss_compiler,\nyou'll be able to write the more simple expression:\n\n```erlang\nPuppies = boss_db:find(puppy, [breed_id = \"breed-47\"])\n```\n\nBossDB supports many query operators, as well as sorting, offsets, and limits;\nsee the API references at the top.\n\nValidating and saving\n---------------------\n\nTo create and save a new record, you would write:\n\n```erlang\nBreed = breed:new(id, \"Golden Retriever\"),\n{ok, SavedBreed} = Breed:save()\n```\n\nYou can provide validation logic by adding a validation_tests/0 function\nto your model file, e.g.\n\n```erlang\n-module(breed, [Id, Name]).\n-has({puppies, many}).\n-export([validation_tests/0]).\n\nvalidation_tests() -\u003e\n    [{fun() -\u003e length(Name) \u003e 0 end,\n        \"Name must not be empty!\"}].\n```\n\nIf validation fails, the save/0 function will return a list of error messages\ninstead of the saved record.\n\nYou can also provide spec strings in the parameter declaration if you want to\nvalidate the attribute types before saving, e.g.\n\n```erlang\n-module(puppy, [Id, Name::string(), BirthDate::datetime()]).\n```\n\nAccepted types are:\n\n* string()\n* binary()\n* datetime()\n* date()\n* timestamp() [e.g. returned by erlang:now()]\n* integer()\n* float()\n\nIf the type validation fails, then validation_tests/0 will not be called.\n\nWorking with existing SQL databases\n-----------------------------------\n\nBy default, SQL columns must be underscored versions of the attribute names,\nand SQL tables must be plural versions of the model names. (If the model is\n\"puppy\", the database table should be \"puppies\".)\n\nYou may want to override these defaults if you are working with an existing\ndatabase. To specify your own column and table names, you can use the\n-columns() and -table() attributes in a model file like so:\n\n```erlang\n-module(puppy, [Id, Name]).\n-columns([{id, \"puppy_id\"}, {name, \"puppy_name\"}]).\n-table(\"puppy_table\").\n```\n\nEvents\n------\n\nBossDB provides two kinds of model events: synchronous save hooks, and\nasynchronous notifications via BossNews. Save hooks are simple; just\ndefine one or more of these functions in your model file:\n\n```erlang\nbefore_create/0 -\u003e ok | {ok, ModifiedRecord} | {error, Reason}\nbefore_update/0 -\u003e ok | {ok, ModifiedRecord} | {error, Reason}\nbefore_update/1 -\u003e ok | {ok, ModifiedRecord} | {error, Reason}\nafter_create/0\nafter_update/0\nafter_update/1\nbefore_delete/0 -\u003e ok | {error, Reason}\n```\n\nThe before_update/1 and after_update/1 hooks provide access to the old\nboss record as an additional parameter. If both hooks like before_update/0\nand before_update/1 exists in a boss model module, before_update/0 will\nbe ignored. The same behaviour applies to after_update/1 and after_update/0.\n\nBossNews is more complicated but also more powerful. It is a notification\nsystem that executes asynchronously, so the code that calls \"save\" does\nnot have to wait for callbacks to complete. The central concept in BossNews\nis a \"watch\", which is an event observer. You can create and destroy watches\nprogrammatically:\n\n```\n{ok, WatchId} = boss_news:watch(TopicString, CallBack),\nboss_news:cancel_watch(WatchId)\n```\nFour kinds of topic strings are supported:\n\n```erlang\n\"puppies\" =\u003e watch for new and deleted Puppy records\n\"puppy-42.*\" =\u003e watch all attributes of Puppy #42\n\"puppy-*.name\" =\u003e watch the \"name\" attribute of all Puppy records\n\"puppy-*.*\" =\u003e watch all attributes of all Puppy records\n```\n\nThe callback is passed two or three arguments: the event name\n(created/updated/deleted), information about the event (i.e. the new and old\nvalues of the watched record), and optionally user information passed as the\nthird argument to boss_news:watch/3.\n\nBossNews is suited to providing real-time notifications and alerts. For example,\nif you want to log each time a puppy's name is changed,\n\n```erlang\nboss_news:watch(\"puppy-*.name\", \n        fun(updated, {Puppy, 'name', OldName, NewName}) -\u003e\n            error_logger:info_msg(\"Puppy's name changed from ~p to ~p\", [OldName, NewName])\n        end)\n```\n\nFor more details see the documentation at http://www.chicagoboss.org/doc/api-news.html\n\n\nCaching\n-------\n\nIf caching is enabled, queries and records are automatically cached. BossDB\nuses BossNews events to automatically invalidate out-of-date cache entries; you do\nnot need to write any cache logic in your save hooks.\n\n\nSharding\n--------\n\nVertical sharding is supported via the db_shards config option. Simply add shard-specific\nconfiguration in a proplist along with an extra config parameter called db_shard_models,\nwhich should be a list of models (atoms) in the shard.\n\n\nPooling\n-------\n\nBossDB uses Poolboy to create a connection pool to the database. Connection pooling\nis supported with all databases.\n\n\nPrimary Keys\n------------\n\nThe Id field of each model is assumed to be an integer supplied by the\ndatabase (e.g., a SERIAL type in Postgres or AUTOINCREMENT in MySQL).\nSpecifying an Id value other than the atom 'id' for a new record will\nresult in an error.\n\nWhen using the mock or pgsql adapters, the Id may have a type of\n::uuid().  This will coerce boss_db into generating a v4 UUID for the\nId field before saving the record (in other words, the UUID is\nprovided by boss_db and not by the application nor by the DB).  UUIDs\nare useful PKs when data are being aggregated from multiple sources.\n\nThe default Id type ::serial() may be explicitly supplied.  Note that\nall Id types, valid or otherwise, pass type validation.\n","funding_links":[],"categories":["ORM and Datamapping","数据库客户端"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FErlyORM%2Fboss_db","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FErlyORM%2Fboss_db","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FErlyORM%2Fboss_db/lists"}