diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 0000000..b69cac9 --- /dev/null +++ b/.credo.exs @@ -0,0 +1,125 @@ +%{ + configs: [ + %{ + name: "default", + files: %{ + included: [ + "lib/", + "src/", + "test/", + "web/", + "apps/*/lib/", + "apps/*/src/", + "apps/*/test/", + "apps/*/web/" + ], + excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] + }, + plugins: [], + requires: [], + strict: true, + parse_timeout: 5000, + color: true, + checks: %{ + enabled: [ + {Credo.Check.Consistency.ExceptionNames, []}, + {Credo.Check.Consistency.LineEndings, []}, + {Credo.Check.Consistency.ParameterPatternMatching, []}, + {Credo.Check.Consistency.SpaceAroundOperators, []}, + {Credo.Check.Consistency.SpaceInParentheses, []}, + {Credo.Check.Consistency.TabsOrSpaces, []}, + {Credo.Check.Design.AliasUsage, [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, + {Credo.Check.Design.TagTODO, [exit_status: 2]}, + {Credo.Check.Design.TagFIXME, []}, + {Credo.Check.Readability.AliasOrder, []}, + {Credo.Check.Readability.FunctionNames, []}, + {Credo.Check.Readability.LargeNumbers, []}, + {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, + {Credo.Check.Readability.ModuleAttributeNames, []}, + {Credo.Check.Readability.ModuleDoc, []}, + {Credo.Check.Readability.ModuleNames, []}, + {Credo.Check.Readability.ParenthesesInCondition, []}, + {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, + {Credo.Check.Readability.PipeIntoAnonymousFunctions, []}, + {Credo.Check.Readability.PredicateFunctionNames, []}, + {Credo.Check.Readability.PreferImplicitTry, []}, + {Credo.Check.Readability.RedundantBlankLines, []}, + {Credo.Check.Readability.Semicolons, []}, + {Credo.Check.Readability.SpaceAfterCommas, []}, + {Credo.Check.Readability.StringSigils, []}, + {Credo.Check.Readability.TrailingBlankLine, []}, + {Credo.Check.Readability.TrailingWhiteSpace, []}, + {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, + {Credo.Check.Readability.VariableNames, []}, + {Credo.Check.Readability.WithSingleClause, []}, + {Credo.Check.Refactor.Apply, []}, + {Credo.Check.Refactor.CondStatements, []}, + {Credo.Check.Refactor.CyclomaticComplexity, []}, + {Credo.Check.Refactor.FunctionArity, []}, + {Credo.Check.Refactor.LongQuoteBlocks, []}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.MapJoin, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, []}, + {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.WithClauses, []}, + {Credo.Check.Refactor.FilterFilter, []}, + {Credo.Check.Refactor.RejectReject, []}, + {Credo.Check.Refactor.RedundantWithClauseResult, []}, + {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, + {Credo.Check.Warning.BoolOperationOnSameValues, []}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, + {Credo.Check.Warning.IExPry, []}, + {Credo.Check.Warning.IoInspect, []}, + {Credo.Check.Warning.OperationOnSameValues, []}, + {Credo.Check.Warning.OperationWithConstantResult, []}, + {Credo.Check.Warning.RaiseInsideRescue, []}, + {Credo.Check.Warning.SpecWithStruct, []}, + {Credo.Check.Warning.WrongTestFileExtension, []}, + {Credo.Check.Warning.UnusedEnumOperation, []}, + {Credo.Check.Warning.UnusedFileOperation, []}, + {Credo.Check.Warning.UnusedKeywordOperation, []}, + {Credo.Check.Warning.UnusedListOperation, []}, + {Credo.Check.Warning.UnusedPathOperation, []}, + {Credo.Check.Warning.UnusedRegexOperation, []}, + {Credo.Check.Warning.UnusedStringOperation, []}, + {Credo.Check.Warning.UnusedTupleOperation, []}, + {Credo.Check.Warning.UnsafeExec, []} + ], + disabled: [ + {Credo.Check.Consistency.MultiAliasImportRequireUse, []}, + {Credo.Check.Consistency.UnusedVariableNames, []}, + {Credo.Check.Design.DuplicatedCode, []}, + {Credo.Check.Design.SkipTestWithoutComment, []}, + {Credo.Check.Readability.AliasAs, []}, + {Credo.Check.Readability.BlockPipe, []}, + {Credo.Check.Readability.ImplTrue, []}, + {Credo.Check.Readability.MultiAlias, []}, + {Credo.Check.Readability.SeparateAliasRequire, []}, + {Credo.Check.Readability.SingleFunctionToBlockPipe, []}, + {Credo.Check.Readability.SinglePipe, []}, + {Credo.Check.Readability.Specs, []}, + {Credo.Check.Readability.StrictModuleLayout, []}, + {Credo.Check.Readability.WithCustomTaggedTuple, []}, + {Credo.Check.Refactor.ABCSize, []}, + {Credo.Check.Refactor.AppendSingleItem, []}, + {Credo.Check.Refactor.DoubleBooleanNegation, []}, + {Credo.Check.Refactor.FilterReject, []}, + {Credo.Check.Refactor.IoPuts, []}, + {Credo.Check.Refactor.MapMap, []}, + {Credo.Check.Refactor.ModuleDependencies, []}, + {Credo.Check.Refactor.NegatedIsNil, []}, + {Credo.Check.Refactor.PipeChainStart, []}, + {Credo.Check.Refactor.RejectFilter, []}, + {Credo.Check.Refactor.VariableRebinding, []}, + {Credo.Check.Warning.LazyLogging, []}, + {Credo.Check.Warning.LeakyEnvironment, []}, + {Credo.Check.Warning.MapGetUnsafePass, []}, + {Credo.Check.Warning.MixEnv, []}, + {Credo.Check.Warning.UnsafeToAtom, []} + ] + } + } + ] +} diff --git a/.formatter.exs b/.formatter.exs index bb09258..3a43859 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,4 +1,4 @@ [ - inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"], + inputs: ["*.{ex,exs}", "{lib,test}/**/*.{ex,exs}"], line_length: 120 ] diff --git a/.gitignore b/.gitignore index b42a6f1..293744c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,8 +13,7 @@ cover deps doc erl_crash.dump -Mnesia.*@* +Mnesia* mnesiac-*.tar mnesiac.iml -MnesiaCore.* test0* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 04887be..3e8b58c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,7 +1,7 @@ minimum_pre_commit_version: 2.7.1 repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v3.4.0 + rev: v4.0.1 hooks: - id: check-added-large-files - id: check-case-conflict diff --git a/.tool-versions b/.tool-versions index 0e709a1..7f1e06b 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -erlang 23.2.5 -elixir 1.11.3-otp-23 +erlang 24.1.7 +elixir 1.13.0-otp-24 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9339135..0000000 --- a/.travis.yml +++ /dev/null @@ -1,52 +0,0 @@ -sudo: false -dist: bionic -language: elixir -elixir: - - 1.8.2 - - 1.9.4 - - 1.10.4 - - 1.11.3 -otp_release: - - 20.3.8.22 - - 21.2 - - 22.3.4 - - 23.1.2 -git: - quiet: true -env: - - PLT_DIR=$HOME/.plt -cache: - directories: - - _build - - deps - - $PLT_DIR -jobs: - exclude: - - elixir: 1.8.2 - otp_release: 23.1.2 - - elixir: 1.9.4 - otp_release: 23.1.2 - - elixir: 1.10.4 - otp_release: 20.3.8.22 - - elixir: 1.11.3 - otp_release: 20.3.8.22 -before_script: - - mkdir -p $PLT_DIR - - mix deps.get --force - - mix deps.compile --force - - mix compile --force - - travis_wait 60 mix dialyzer --plt - - MIX_ENV=test mix deps.get --force - - MIX_ENV=test mix deps.compile --force -script: - - mix credo --strict --all - - MIX_ENV=test mix compile --force --warnings-as-errors - - MIX_ENV=test mix format --check-formatted --dry-run - - mix purge.db - - MIX_ENV=test mix coveralls.json --no-start -after_success: - - bash <(curl -s https://codecov.io/bash) - - mix inch.report -notifications: - email: - - randy@heroictek.com diff --git a/CHANGELOG.md b/CHANGELOG.md index f8902f6..08c0e18 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,31 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [[0.4.0]] - 2021-12-08 +### Added +- Ability to specify how many copies of schema can exist, by type. +- Ability to specify how many copies of a store can exist, per store by type. +- Ability to blacklist Mnesia cluster nodes, per store. +- `init_schema/1`, `copy_schema/2`, `refresh_cluster/1` overridable callbacks in `Mnesiac.Store`. +- Structs for explicit config and store contracts. +- Specs for explicit library contracts. +- Escape hatch of sorts, which allows you to override the base Mnesiac configuration struct. +- `Mnesiac.validate_config/2` which can be used to validate the configuration being passed in to Mnesiac, possible to test override functions too. + +### Removed +- Removed `Mnesiac.StoreManager` module. +- Removed config directory. Configuration is now to be passed in directly. + +### Changed +- `resolve_conflict/1` -> `resolve_conflict/2`, now accepts configuration. +- Standardized terminology in library. +- Improved Travis CI jobs. +- Bumped OTP and Elixir version. +- Updated dependencies. + +### Fixed +- More docs cleanup. + ## [[0.3.9]] - 2021-02-21 ### Changed - Bumped OTP version. @@ -59,7 +84,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [[0.3.2]] - 2019-02-22 ### Fixed -- Regression that made defining a custom table name impossible. +- Regression that made defining a custom store name impossible. ## [[0.3.1]] - 2019-02-21 ### Added @@ -92,6 +117,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Initial release. +[0.4.0]: https://github.com/beardedeagle/mnesiac/compare/v0.3.9...v0.4.0 [0.3.9]: https://github.com/beardedeagle/mnesiac/compare/v0.3.8...v0.3.9 [0.3.8]: https://github.com/beardedeagle/mnesiac/compare/v0.3.7...v0.3.8 [0.3.7]: https://github.com/beardedeagle/mnesiac/compare/v0.3.6...v0.3.7 diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 77ff1bd..12af8f1 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -3,3 +3,4 @@ - astutecat - darrenclark - aby2503 +- davidwebster48 diff --git a/README.md b/README.md index 2f91bee..ec7f553 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Mnesia auto clustering made easy! Docs can be found at [https://hexdocs.pm/mnesiac](https://hexdocs.pm/mnesiac). -**_NOTICE:_** Mnesiac, while stable, is still considered pre `1.0`. This means the API can, and may, change at any time. Please ensure you review the docs and changelog prior to updating. +**_NOTICE:_** Mnesiac, while stable, is still considered pre `1.0`. This means the API can, and may, change at any time. Please ensure you review the docs and changelog prior to updating, or pin the version of mnesiac you are using in `mix.exs` if necessary. **_NOTICE:_** Mnesiac allows a significant amount of freedom with how it behaves. This allows you to customize Mnesiac to suit your needs. However, this also allows for a fair amount of foot gunning. Please ensure you've done your due diligence when using this library, or Mnesia itself for that matter. It isn't a silver bullet, and it shouldn't be treated as one. @@ -17,21 +17,22 @@ Simply add `mnesiac` to your list of dependencies in `mix.exs`: ```elixir def deps do [ - {:mnesiac, "~> 0.3"} + {:mnesiac, "~> 0.4"} ] end ``` -Edit your app's config.exs to add the list of Mnesia stores: +Then add `mnesiac` to your supervision tree, passing in the cluster and the Mnesiac configuration: -```elixir -config :mnesiac, - stores: [Mnesiac.ExampleStore, ...], - schema_type: :disc_copies, # defaults to :ram_copies - table_load_timeout: 600_000 # milliseconds, default is 600_000 -``` +- Supported types: + - ram_copies. + - disc_copies. + - disc_only_copies. -Then add `mnesiac` to your supervision tree: +- Supported replication types: + - **_N_** nodes in Mnesia cluster (represented as positive integers). + - **_N%_** of nodes in Mnesia cluster (represented as `N.NN` floats, where `1.00` would be 100%). + - **_SPECIFIC_** nodes in Mnesia cluster (valid node names only). - **_EXAMPLE:_** With `libcluster` using the `Cluster.Strategy.Epmd` strategy: @@ -39,11 +40,31 @@ Then add `mnesiac` to your supervision tree: ... topology = Application.get_env(:libcluster, :topologies) - hosts = topology[:myapp][:config][:hosts] + cluster = topology[:myapp][:config][:hosts] + config = [ + schema: [ # default is :ram_copies, everywhere + disc_copies: [:n3@local, :n4@local, :n6@local], + ram_copies: [:n10@local, :n11@local] + ], + stores: [ + [ # default is :ram_copies, everywhere + ref: MyApp.ExampleStore, + disc_copies: [:n3@local, :n4@local, :n6@local], + ram_copies: [:n10@local, :n11@local], + blacklist: [:n10@local, :n11@local] + ], + [ + ref: MyApp.ExampleStoreTwo, + disc_copies: [:n10@local, :n11@local], + ram_copies: [:n3@local, :n4@local, :n6@local] + ] + ], + store_load_timeout: 600_000 + ] children = [ {Cluster.Supervisor, [topology, [name: MyApp.ClusterSupervisor]]}, - {Mnesiac.Supervisor, [hosts, [name: MyApp.MnesiacSupervisor]]}, + {Mnesiac.Supervisor, [[cluster: cluster, config: config], [name: MyApp.MnesiacSupervisor]]}, ... ] @@ -59,7 +80,29 @@ Then add `mnesiac` to your supervision tree: { Mnesiac.Supervisor, [ - [:"test01@127.0.0.1", :"test02@127.0.0.1"], + [ + cluster: [:n3@local, :n4@local], + config: [ + schema: [ # default is :ram_copies, everywhere + disc_copies: [:n3@local, :n4@local, :n6@local], + ram_copies: [:n10@local, :n11@local] + ], + stores: [ + [ # default is :ram_copies, everywhere + ref: MyApp.ExampleStore, + disc_copies: [:n3@local, :n4@local, :n6@local], + ram_copies: [:n10@local, :n11@local], + blacklist: [:n10@local, :n11@local] + ], + [ + ref: MyApp.ExampleStoreTwo, + disc_copies: [:n10@local, :n11@local], + ram_copies: [:n3@local, :n4@local, :n6@local] + ] + ], + store_load_timeout: 600_000 + ] + ], [name: MyApp.MnesiacSupervisor] ] }, @@ -71,19 +114,22 @@ Then add `mnesiac` to your supervision tree: ## Usage -### Table creation +### Store creation -Create a table store, `use Mnesiac.Store`, and add it to your app's config.exs. +To create a store, `use Mnesiac.Store`, and ensure it's added to the config for Mnesiac you're passing in. All stores **_MUST_** implement its own `store_options/0`, which returns a keyword list of store options. -There are three optional callbacks which can be implemented: +There are nine optional callbacks which can be implemented: -- `init_store/0`, which allows users to implement custom store initialization logic. Triggered by Mnesiac. -- `copy_store/0`, which allows users to implement a custom call to copy a store. Triggered by Mnesiac. -- `resolve_conflict/1`, which allows a user to implement logic when Mnesiac detects a store with records on both the local and remote Mnesia cluster node. Triggered by Mnesiac. Default is to do nothing. +- `init_schema/1`, which allows users to implement custom schema initialization logic. Triggered by Mnesiac. +- `copy_schema/2`, which allows users to implement a custom call to copy schema. Triggered by Mnesiac. +- `init_store/1`, which allows users to implement custom store initialization logic. Triggered by Mnesiac. +- `copy_store/1`, which allows users to implement a custom call to copy a store. Triggered by Mnesiac. +- `refresh_cluster/1`, which allows users to implement custom logic to refresh Mnesia cluster. Triggered by user. Default is to do nothing. +- `resolve_conflict/2`, which allows a user to implement logic when Mnesiac detects a store with records on both the local and remote Mnesia cluster node. Triggered by Mnesiac. Default is to do nothing. -**_MINIMAL EXAMPLE:_**: +**_MINIMAL EXAMPLE:_** ```elixir defmodule MyApp.ExampleStore do @@ -96,7 +142,7 @@ defmodule MyApp.ExampleStore do @doc """ Record definition for ExampleStore example record. """ - Record.defrecord( + defrecord( :example, __MODULE__, id: nil, @@ -118,7 +164,7 @@ defmodule MyApp.ExampleStore do @impl true def store_options, do: [ - record_name: __MODULE__, + record_name: :example, attributes: example() |> example() |> Keyword.keys(), index: [:topic_id], ram_copies: [node()] @@ -151,12 +197,20 @@ mix compile --force **_NOTICE:_** You can find the `asdf` tool [here][1]. +## Linting and static analysis + +Mnesiac provides a single command for linting and static analysis: + +```shell +mix check +``` + ## Testing Before you run any tests, ensure that you have cleaned up Mnesia: ```shell -mix purge.db +mix db.purge ``` Test results and coverage reports are generated by running the following: diff --git a/SECURITY.md b/SECURITY.md index c94794c..bb25a1a 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,4 +6,4 @@ As it currently stands, this project does not have a supported version other tha ## Reporting a Vulnerability -If you discover a vulnerability in the Mnesiac codebase, please email the details and method to reproduce to randy@heroictek.com. You should expect a response within 48 - 72 hours, with followups as nessasary. If the vulnerability is accepted, an advisory will be drafted for it for Mnesiac and an issue to fix it will be created, if possible. If the issue turns out to be caused by an underlying library or language, an issue will be filed with them. You should expect a response with steps taken whether the vulnerability is accepted or declined. +If you discover a vulnerability in the Mnesiac codebase, please email the details and method to reproduce to randy@heroictek.com. You should expect a response within 48 - 72 hours, with followups as necessary. If the vulnerability is accepted, an advisory will be drafted for it for Mnesiac and an issue to fix it will be created, if possible. If the issue turns out to be caused by an underlying library or language, an issue will be filed with them. You should expect a response with steps taken whether the vulnerability is accepted or declined. diff --git a/config/.credo.exs b/config/.credo.exs deleted file mode 100644 index 9ad72dc..0000000 --- a/config/.credo.exs +++ /dev/null @@ -1,105 +0,0 @@ -%{ - configs: [ - %{ - name: "default", - files: %{ - included: [ - "lib/", - "src/", - "test/", - "web/", - "apps/*/lib/", - "apps/*/src/", - "apps/*/test/", - "apps/*/web/" - ], - excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] - }, - plugins: [], - requires: [], - strict: true, - parse_timeout: 5000, - color: true, - checks: [ - {Credo.Check.Consistency.ExceptionNames, []}, - {Credo.Check.Consistency.LineEndings, []}, - {Credo.Check.Consistency.ParameterPatternMatching, []}, - {Credo.Check.Consistency.SpaceAroundOperators, []}, - {Credo.Check.Consistency.SpaceInParentheses, []}, - {Credo.Check.Consistency.TabsOrSpaces, []}, - {Credo.Check.Design.AliasUsage, [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, - {Credo.Check.Design.TagTODO, [exit_status: 2]}, - {Credo.Check.Design.TagFIXME, []}, - {Credo.Check.Readability.AliasOrder, []}, - {Credo.Check.Readability.FunctionNames, []}, - {Credo.Check.Readability.LargeNumbers, []}, - {Credo.Check.Readability.MaxLineLength, [priority: :low, max_length: 120]}, - {Credo.Check.Readability.ModuleAttributeNames, []}, - {Credo.Check.Readability.ModuleDoc, []}, - {Credo.Check.Readability.ModuleNames, []}, - {Credo.Check.Readability.ParenthesesInCondition, []}, - {Credo.Check.Readability.ParenthesesOnZeroArityDefs, []}, - {Credo.Check.Readability.PredicateFunctionNames, []}, - {Credo.Check.Readability.PreferImplicitTry, []}, - {Credo.Check.Readability.RedundantBlankLines, []}, - {Credo.Check.Readability.Semicolons, []}, - {Credo.Check.Readability.SpaceAfterCommas, []}, - {Credo.Check.Readability.StringSigils, []}, - {Credo.Check.Readability.TrailingBlankLine, []}, - {Credo.Check.Readability.TrailingWhiteSpace, []}, - {Credo.Check.Readability.UnnecessaryAliasExpansion, []}, - {Credo.Check.Readability.VariableNames, []}, - {Credo.Check.Refactor.CondStatements, []}, - {Credo.Check.Refactor.CyclomaticComplexity, []}, - {Credo.Check.Refactor.FunctionArity, []}, - {Credo.Check.Refactor.LongQuoteBlocks, []}, - {Credo.Check.Refactor.MatchInCondition, []}, - {Credo.Check.Refactor.NegatedConditionsInUnless, []}, - {Credo.Check.Refactor.NegatedConditionsWithElse, []}, - {Credo.Check.Refactor.Nesting, []}, - {Credo.Check.Refactor.UnlessWithElse, []}, - {Credo.Check.Refactor.WithClauses, []}, - {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, - {Credo.Check.Warning.BoolOperationOnSameValues, []}, - {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, - {Credo.Check.Warning.IExPry, []}, - {Credo.Check.Warning.IoInspect, []}, - {Credo.Check.Warning.MixEnv, false}, - {Credo.Check.Warning.OperationOnSameValues, []}, - {Credo.Check.Warning.OperationWithConstantResult, []}, - {Credo.Check.Warning.RaiseInsideRescue, []}, - {Credo.Check.Warning.UnusedEnumOperation, []}, - {Credo.Check.Warning.UnusedFileOperation, []}, - {Credo.Check.Warning.UnusedKeywordOperation, []}, - {Credo.Check.Warning.UnusedListOperation, []}, - {Credo.Check.Warning.UnusedPathOperation, []}, - {Credo.Check.Warning.UnusedRegexOperation, []}, - {Credo.Check.Warning.UnusedStringOperation, []}, - {Credo.Check.Warning.UnusedTupleOperation, []}, - {Credo.Check.Warning.UnsafeExec, []}, - {Credo.Check.Consistency.MultiAliasImportRequireUse, false}, - {Credo.Check.Consistency.UnusedVariableNames, false}, - {Credo.Check.Design.DuplicatedCode, false}, - {Credo.Check.Readability.AliasAs, false}, - {Credo.Check.Readability.BlockPipe, false}, - {Credo.Check.Readability.ImplTrue, false}, - {Credo.Check.Readability.MultiAlias, false}, - {Credo.Check.Readability.SeparateAliasRequire, false}, - {Credo.Check.Readability.SinglePipe, false}, - {Credo.Check.Readability.Specs, false}, - {Credo.Check.Readability.StrictModuleLayout, false}, - {Credo.Check.Readability.WithCustomTaggedTuple, false}, - {Credo.Check.Refactor.ABCSize, false}, - {Credo.Check.Refactor.AppendSingleItem, false}, - {Credo.Check.Refactor.DoubleBooleanNegation, false}, - {Credo.Check.Refactor.ModuleDependencies, false}, - {Credo.Check.Refactor.NegatedIsNil, false}, - {Credo.Check.Refactor.PipeChainStart, false}, - {Credo.Check.Refactor.VariableRebinding, false}, - {Credo.Check.Warning.LeakyEnvironment, false}, - {Credo.Check.Warning.MapGetUnsafePass, false}, - {Credo.Check.Warning.UnsafeToAtom, false} - ] - } - ] -} diff --git a/config/config.exs b/config/config.exs deleted file mode 100644 index 8233fe9..0000000 --- a/config/config.exs +++ /dev/null @@ -1,3 +0,0 @@ -use Mix.Config - -import_config "#{Mix.env()}.exs" diff --git a/config/dev.exs b/config/dev.exs deleted file mode 100644 index 7fe05c6..0000000 --- a/config/dev.exs +++ /dev/null @@ -1,6 +0,0 @@ -use Mix.Config - -config :mnesiac, - stores: [], - schema_type: :ram_copies, - table_load_timeout: 600_000 diff --git a/config/prod.exs b/config/prod.exs deleted file mode 100644 index 7fe05c6..0000000 --- a/config/prod.exs +++ /dev/null @@ -1,6 +0,0 @@ -use Mix.Config - -config :mnesiac, - stores: [], - schema_type: :ram_copies, - table_load_timeout: 600_000 diff --git a/config/test.exs b/config/test.exs deleted file mode 100644 index 9e53692..0000000 --- a/config/test.exs +++ /dev/null @@ -1,9 +0,0 @@ -use Mix.Config - -config :mnesia, - dir: to_charlist(Path.join(File.cwd!(), to_string(node()))) - -config :mnesiac, - stores: [Mnesiac.Support.ExampleStore], - schema_type: :disc_copies, - table_load_timeout: 600_000 diff --git a/lib/mnesiac.ex b/lib/mnesiac.ex index 7d8a5d0..cd12a8c 100644 --- a/lib/mnesiac.ex +++ b/lib/mnesiac.ex @@ -1,132 +1,245 @@ defmodule Mnesiac do @moduledoc """ - Mnesiac Manager + Mnesiac Manager. """ require Logger - alias Mnesiac.StoreManager - @doc """ - Start Mnesia with strict host checking + @typedoc """ + Default arguments expected to be passed in to Mnesiac. `override` is optional. """ - def init_mnesia(nodes) do - nodes = - Enum.filter(Node.list(), fn node -> - node in List.flatten(nodes) - end) - - case nodes do - [head | _tail] -> - join_cluster(head) - - [] -> - start() - end - end + @type arg :: + {:cluster, [node()]} + | {:config, config} + | {:override, (config -> {:ok, struct()} | {:error, term()}) | nil} + + @typedoc """ + Default implementation of arguments expected to be passed in to Mnesiac. + """ + @type init_arg :: [arg] + + @typedoc """ + Default configuration expected to be passed in to Mnesiac. `store_load_timeout` is optional. + """ + @type config :: + {:schema, Mnesiac.Store.config()} + | {:stores, [Mnesiac.Store.config(), ...]} + | {:store_load_timeout, integer()} + + @typedoc """ + Defines the configuration for mnesiac. + + ## Example + + ```elixir + %Mnesiac{ + schema: %Mnesiac.Store{ + blacklist: [], + disc_copies: [], + disc_only_copies: [], + ram_copies: [1.0], + ref: Mnesiac.Store + }, + store_load_timeout: 600000, + stores: [ + %Mnesiac.Store{ + blacklist: [:n10@local, :n11@local], + disc_copies: [:n3@local, :n4@local, :n6@local], + disc_only_copies: [], + ram_copies: [:n10@local, :n11@local], + ref: Mnesiac.ExampleStore + }, + %Mnesiac.Store{ + blacklist: [], + disc_copies: [:n10@local, :n11@local], + disc_only_copies: [], + ram_copies: [:n3@local, :n4@local, :n6@local], + ref: Mnesiac.ExampleStoreTwo + } + ] + } + ``` + """ + @type t :: %__MODULE__{ + schema: %Mnesiac.Store{}, + stores: [%Mnesiac.Store{}, ...], + store_load_timeout: integer() + } + + @enforce_keys [:schema, :stores] + defstruct schema: nil, + stores: [], + store_load_timeout: 600_000 @doc """ - Start Mnesia alone + Initialize Mnesia. Filters out cluster nodes not explicitly passed in. + + ## Example + + ```elixir + config = [ + schema: [ + disc_copies: [node3, node4, node6], + ram_copies: [node10, node11] + ], + stores: [ + [ref: Mnesiac.ExampleStore, disc_copies: [node3, node4, node6], ram_copies: [node10, node11], blacklist: [node1, node2]] + ], + store_load_timeout: 600_000 + ] + Mnesiac.init_mnesia([cluster: [node()], config: config]) + :ok + ``` """ - def start do - with :ok <- ensure_dir_exists(), - :ok <- ensure_started(), - :ok <- StoreManager.copy_schema(node()), - :ok <- StoreManager.init_tables(), - :ok <- StoreManager.ensure_tables_loaded() do - :ok - else - {:error, reason} -> - Logger.debug(fn -> "[mnesiac:#{node()}] #{inspect(reason)}" end) - {:error, reason} + @spec init_mnesia(Mnesiac.init_arg()) :: :ok | {:error, term()} + def init_mnesia(cluster: cluster, config: config, override: override) do + case filter_cluster(cluster) do + [head | _tail] -> join_cluster(config, override, head) + [] -> start(config, override) end end + def init_mnesia(cluster: cluster, config: config), do: init_mnesia(cluster: cluster, config: config, override: nil) + @doc """ - Join to a Mnesia cluster + Validate configuration being passed in to Mnesiac will build to a proper Mneisac configuration struct. + + ## Example + + ```elixir + Mnesiac.validate_config(config) + {:ok, + %Mnesiac{ + schema: %Mnesiac.Store{ + blacklist: [], + disc_copies: [:n3@local, :n4@local, :n6@local], + disc_only_copies: [], + ram_copies: [:n10@local, :n11@local], + ref: Mnesiac.Store + }, + store_load_timeout: 600000, + stores: [ + %Mnesiac.Store{ + blacklist: [:n10@local, :n11@local], + disc_copies: [:n3@local, :n4@local, :n6@local], + disc_only_copies: [], + ram_copies: [:n10@local, :n11@local], + ref: Mnesiac.ExampleStore + }, + %Mnesiac.Store{ + blacklist: [], + disc_copies: [:n10@local, :n11@local], + disc_only_copies: [], + ram_copies: [:n3@local, :n4@local, :n6@local], + ref: Mnesiac.ExampleStoreTwo + } + ] + } + } + ``` """ - def join_cluster(cluster_node) do - with :ok <- ensure_dir_exists(), - :ok <- ensure_stopped(), - :ok <- StoreManager.delete_schema(), - :ok <- ensure_started(), - :ok <- connect(cluster_node), - :ok <- StoreManager.copy_schema(node()), - :ok <- StoreManager.copy_tables(cluster_node), - :ok <- StoreManager.ensure_tables_loaded() do - :ok - else - {:error, reason} -> - Logger.debug(fn -> "[mnesiac:#{node()}] #{inspect(reason)}" end) - {:error, reason} - end - end + @spec validate_config( + config :: config(), + override :: (config() -> {:ok, struct()} | {:error, term()}) | nil + ) :: {:ok, struct()} | {:error, term()} + def validate_config(config, override \\ nil), do: build_struct(config, override) @doc """ - Cluster status + Get the cluster status. + + ## Example + + ```elixir + Mnesiac.cluster_status() + {:ok, [running_nodes: [:nonode@nohost]]} + ``` """ + @spec cluster_status() :: + {:ok, [{:running_nodes, [node()]}]} | {:ok, [{:running_nodes, [node()]}, {:stopped_nodes, [node()]}]} def cluster_status do running = :mnesia.system_info(:running_db_nodes) stopped = :mnesia.system_info(:db_nodes) -- running if stopped == [] do - [{:running_nodes, running}] + {:ok, [{:running_nodes, running}]} else - [{:running_nodes, running}, {:stopped_nodes, stopped}] + {:ok, [{:running_nodes, running}, {:stopped_nodes, stopped}]} end end @doc """ - Cluster with a node - """ - def connect(cluster_node) do - case :mnesia.change_config(:extra_db_nodes, [cluster_node]) do - {:ok, [_cluster_node]} -> - :ok + Returns a list of running Mnesia cluster nodes. - {:ok, []} -> - {:error, {:failed_to_connect_node, cluster_node}} + ## Example - {:error, reason} -> - {:error, reason} - end - end - - @doc """ - Running Mnesia nodes + ```elixir + Mnesiac.running_nodes() + {:ok, [:nonode@nohost]} + ``` """ - def running_nodes do - :mnesia.system_info(:running_db_nodes) - end + @spec running_nodes() :: {:ok, [node()]} + def running_nodes, do: {:ok, :mnesia.system_info(:running_db_nodes)} @doc """ - Is node in Mnesia cluster? + Is this node in the Mnesia cluster? + + ## Example + + ```elixir + Mnesiac.node_in_cluster?(node()) + true + ``` """ - def node_in_cluster?(cluster_node) do - Enum.member?(:mnesia.system_info(:db_nodes), cluster_node) - end + @spec node_in_cluster?(cluster_node :: node()) :: true | false + def node_in_cluster?(cluster_node), do: Enum.member?(:mnesia.system_info(:db_nodes), cluster_node) @doc """ - Is running Mnesia node? + Is this node running Mnesia? + + ## Example + + ```elixir + Mnesiac.running_db_node?(node()) + true + ``` """ + @spec running_db_node?(cluster_node :: node()) :: true | false def running_db_node?(cluster_node) do - Enum.member?(running_nodes(), cluster_node) + {:ok, cluster} = running_nodes() + Enum.member?(cluster, cluster_node) end - defp ensure_started do - with :ok <- start_server(), - :ok <- wait_for(:start) do + defp filter_cluster(nodes), do: Enum.filter(Node.list(), fn node -> node in List.flatten(nodes) end) + + defp join_cluster(config, override, cluster_node) do + with {:ok, config_struct} <- build_struct(config, override), + :ok <- ensure_dir_exists(), + :ok <- ensure_stopped(), + :ok <- :mnesia.delete_schema([node()]), + :ok <- ensure_started(), + :ok <- connect(cluster_node), + :ok <- copy_schema(config_struct, cluster_node), + :ok <- copy_tables(config_struct, cluster_node), + :ok <- ensure_tables_loaded(config_struct) do :ok else {:error, reason} -> + Logger.debug(fn -> "[mnesiac:#{node()}] #{inspect(reason)}" end) {:error, reason} end end - defp ensure_stopped do - with :stopped <- stop_server(), - :ok <- wait_for(:stop) do + defp start(config, override) do + with {:ok, config_struct} <- build_struct(config, override), + :ok <- ensure_dir_exists(), + :ok <- ensure_stopped(), + :ok <- ensure_started(), + :ok <- init_schema(config_struct), + :ok <- init_tables(config_struct), + :ok <- ensure_tables_loaded(config_struct) do :ok else {:error, reason} -> + Logger.debug(fn -> "[mnesiac:#{node()}] #{inspect(reason)}" end) {:error, reason} end end @@ -138,20 +251,115 @@ defmodule Mnesiac do :ok <- File.mkdir(mnesia_dir) do :ok else - true -> - :ok + true -> :ok + {:error, reason} -> {:error, reason} + end + end - {:error, reason} -> - {:error, reason} + defp ensure_stopped do + with :stopped <- :mnesia.stop(), + :ok <- wait_for(:stop) do + :ok + else + {:error, reason} -> {:error, reason} end end - defp start_server do - :mnesia.start() + defp build_struct(config, nil) do + {:ok, + struct!( + __MODULE__, + schema: build_struct(config, :schema), + stores: Enum.map(Keyword.get(config, :stores, []), &struct!(Mnesiac.Store, &1)), + store_load_timeout: Keyword.get(config, :store_load_timeout, 600_000) + )} end - defp stop_server do - :mnesia.stop() + defp build_struct(config, :schema) do + struct!( + Mnesiac.Store, + Keyword.merge( + Keyword.get(config, :schema, ram_copies: [1.0]), + ref: Mnesiac.Store + ) + ) + end + + defp build_struct(config, override) when is_function(override, 1) do + case override.(config) do + {:ok, config_struct} -> {:ok, config_struct} + {:error, reason} -> {:error, reason} + end + end + + defp build_struct(_config, _unsupported), do: {:error, {:build_struct_failed, :unsupported_override_type}} + + defp ensure_started do + with :ok <- :mnesia.start(), + :ok <- wait_for(:start) do + :ok + else + {:error, reason} -> {:error, reason} + end + end + + defp connect(cluster_node) do + case :mnesia.change_config(:extra_db_nodes, [cluster_node]) do + {:ok, [_cluster_node]} -> :ok + {:ok, []} -> {:error, {:failed_to_connect_node, cluster_node}} + {:error, reason} -> {:error, reason} + end + end + + defp copy_schema(config, cluster_node) do + apply(config.schema.ref, :copy_schema, [config, cluster_node]) + end + + defp init_schema(config), do: apply(config.schema.ref, :init_schema, [config]) + + defp copy_tables(config, cluster_node) do + local_cookies = get_table_cookies() + remote_cookies = get_table_cookies(cluster_node) + + Enum.each(config.stores, fn store -> + cookie = Keyword.get(store.ref.store_options(), :record_name, store.ref) + + case {local_cookies[cookie], remote_cookies[cookie]} do + {nil, nil} -> + apply(store.ref, :init_store, [store]) + + {nil, _} -> + apply(store.ref, :copy_store, [store]) + + {_, nil} -> + {:error, {:no_remote_records_to_copy, store.ref, cluster_node}} + + {_local, _remote} -> + store_name = Keyword.get(config.ref.store_options(), :record_name, __MODULE__) + Logger.info("[mnesiac:#{node()}] #{inspect(store_name)}: records found on both sides, copy aborted.") + apply(store.ref, :resolve_conflict, [config, cluster_node]) + end + end) + end + + defp init_tables(config) do + case :mnesia.system_info(:extra_db_nodes) do + [head | _tail] -> copy_tables(config, head) + [] -> create_tables(config) + {:aborted, reason} -> {:error, reason} + end + end + + defp create_tables(config), do: Enum.each(config.stores, fn store -> apply(store.ref, :init_store, [store]) end) + + defp ensure_tables_loaded(config) do + tables = :mnesia.system_info(:local_tables) + + case :mnesia.wait_for_tables(tables, config.store_load_timeout) do + :ok -> :ok + {:error, reason} -> {:error, reason} + {:timeout, bad_tables} -> {:error, {:timeout, bad_tables}} + end end defp wait_for(:start) do @@ -187,4 +395,12 @@ defmodule Mnesiac do wait_for(:stop) end end + + defp get_table_cookies(node \\ node()) do + tables = :rpc.call(node, :mnesia, :system_info, [:tables]) + + Enum.reduce(tables, %{}, fn table, acc -> + Map.put(acc, table, :rpc.call(node, :mnesia, :table_info, [table, :cookie])) + end) + end end diff --git a/lib/mnesiac/store.ex b/lib/mnesiac/store.ex index ca66b2a..d5007f5 100644 --- a/lib/mnesiac/store.ex +++ b/lib/mnesiac/store.ex @@ -1,39 +1,111 @@ defmodule Mnesiac.Store do @moduledoc """ - This module defines a mnesiac store and contains overridable callbacks. + Defines an mnesiac store and contains overridable callbacks. """ + @typedoc """ + Default store configuration expected to be passed in to Mnesiac. Everything is optional except `ref`. + """ + @type config :: + {:ref, module() | atom()} + | {:ram_copies, [non_neg_integer() | float() | node()]} + | {:disc_copies, [non_neg_integer() | float() | node()]} + | {:disc_only_copies, [non_neg_integer() | float() | node()]} + | {:blacklist, [node()]} + + @typedoc """ + Defines the configuration of an mnesiac store. + + ## Example + + ```elixir + %Mnesiac.Store{ + blacklist: [:"test22@127.0.0.1"], + disc_copies: [1.0], + disc_only_copies: [], + ram_copies: [], + ref: Mnesiac.Support.ExampleStore + } + ``` + """ + @type t :: %__MODULE__{ + ref: module() | atom(), + ram_copies: [non_neg_integer() | float() | node()], + disc_copies: [non_neg_integer() | float() | node()], + disc_only_copies: [non_neg_integer() | float() | node()], + blacklist: [node()] + } + + @enforce_keys [:ref] + defstruct ref: nil, + ram_copies: [], + disc_copies: [], + disc_only_copies: [], + blacklist: [] + @doc """ - This function returns ths store's configuration as a keyword list. - For more information on the options supported here, see mnesia's documenatation. - ## Examples + Returns the store's configuration as a keyword list. + For more information on the options supported here, please see mnesia's documentation. + + ## Example + ```elixir - iex> store_options() + store_options() [attributes: [...], index: [:topic_id], disc_copies: [node()]] ``` - **Note**: Defining `:record_name` in `store_options()` will set the mnesia table name to the same. + + **Note**: Defining `:record_name` in `store_options()` will set the mnesia store name to the same. + """ + @callback store_options() :: keyword() + + @doc """ + Called by mnesiac to initialize mnesia's schema. + + ## Default Implementation + + ```elixir + def init_schema(config) do + :ok + end + ``` """ - @callback store_options() :: term + @callback init_schema(config :: struct()) :: term() @doc """ - This function is called by mnesiac either when it has no existing data to use or copy and will initialise a table + Called by mnesiac to copy mnesia's schema. + ## Default Implementation + ```elixir - def init_store do + def copy_schema(config) do + :ok + end + ``` + """ + @callback copy_schema(config :: struct(), cluster_node :: node()) :: term() + + @doc """ + Called by mnesiac either when it has no existing records to use or copy and will initialize a store. + + ## Default Implementation + + ```elixir + def init_store(config) do :mnesia.create_table(Keyword.get(store_options(), :record_name, __MODULE__), store_options()) end ``` """ - @callback init_store() :: term + @callback init_store(config :: struct()) :: term() @doc """ - This function is called by mnesiac when it joins a mnesia cluster and data for this store is found on the remote node in the cluster that is being connected to. + Called by mnesiac when it joins a mnesia cluster and records for this store is found on the remote node being connected to. + ## Default Implementation + ```elixir - def copy_store do + def copy_store(config) do for type <- [:ram_copies, :disc_copies, :disc_only_copies] do value = Keyword.get(store_options(), type, []) - if Enum.member?(value, node()) do :mnesia.add_table_copy(Keyword.get(store_options(), :record_name, __MODULE__), node(), type) end @@ -41,50 +113,121 @@ defmodule Mnesiac.Store do end ``` """ - @callback copy_store() :: term + @callback copy_store(config :: struct()) :: term() + + @doc """ + Called by user when the cluster needs to be refreshed. - @doc ~S""" - This function is called by mnesiac when it has detected data for a table on both the local node and the remote node of the cluster it is connecting to. ## Default Implementation + ```elixir - def resolve_conflict(cluster_node) do - table_name = Keyword.get(store_options(), :record_name, __MODULE__) - Logger.info(fn -> "[mnesiac:#{node()}] #{inspect(table_name)}: data found on both sides, copy aborted." end) + def refresh_cluster(config) do + :ok + end + ``` + + **Note**: The default implementation is to do nothing. + """ + @callback refresh_cluster(config :: struct()) :: term() + + @doc ~S""" + Called by mnesiac when it has detected records for a store on both the local and remote nodes it is connecting to. + ## Default Implementation + + ```elixir + def resolve_conflict(_config, cluster_node) do :ok end ``` - **Note**: The default implementation for this function is to do nothing. + + **Note**: The default implementation is to do nothing. """ - @callback resolve_conflict(node()) :: term + @callback resolve_conflict(config :: struct(), cluster_node :: node()) :: term() - @optional_callbacks copy_store: 0, init_store: 0, resolve_conflict: 1 + @optional_callbacks init_schema: 1, + copy_schema: 2, + init_store: 1, + copy_store: 1, + refresh_cluster: 1, + resolve_conflict: 2 - defmacro __using__(_) do - quote do - require Logger - @behaviour Mnesiac.Store + @doc """ + Called by mnesiac to initialize mnesia's schema. + """ + @spec init_schema(Store.t()) :: :ok + def init_schema(config), do: copy_schema(config, node()) - def init_store do - :mnesia.create_table(Keyword.get(store_options(), :record_name, __MODULE__), store_options()) + @doc """ + Called by mnesiac to copy mnesia's schema. + """ + @spec copy_schema(Store.t(), node()) :: :ok + def copy_schema(config, cluster_node) do + Enum.each(Map.from_struct(config.schema), fn {type, nodes} -> + if type in [:ram_copies, :disc_copies, :disc_only_copies] do + maybe_apply_schema(type, cluster_node, nodes) end + end) + end + + @doc """ + Called by mnesiac either when it has no existing records to use or copy and will initialize a store. + """ + @spec init_store(Store.t()) :: {:aborted, term()} | {:atomic, :ok} + def init_store(config) do + :mnesia.create_table(Keyword.get(config.ref.store_options(), :record_name, __MODULE__), config.ref.store_options()) + end - def copy_store do - for type <- [:ram_copies, :disc_copies, :disc_only_copies] do - value = Keyword.get(store_options(), type, []) + @doc """ + Called by mnesiac when it joins a mnesia cluster and records for this store is found on the remote node being connected to. + """ + @spec copy_store(Store.t()) :: [term()] + def copy_store(config) do + for type <- [:ram_copies, :disc_copies, :disc_only_copies] do + value = Keyword.get(config.ref.store_options(), type, []) - if Enum.member?(value, node()) do - :mnesia.add_table_copy(Keyword.get(store_options(), :record_name, __MODULE__), node(), type) - end - end + if Enum.member?(value, node()) do + :mnesia.add_table_copy(Keyword.get(config.ref.store_options(), :record_name, __MODULE__), node(), type) end + end + end - def resolve_conflict(cluster_node) do - table_name = Keyword.get(store_options(), :record_name, __MODULE__) - Logger.info(fn -> "[mnesiac:#{node()}] #{inspect(table_name)}: data found on both sides, copy aborted." end) + @doc """ + Called by user when the cluster needs to be refreshed. - :ok + **Note**: The default implementation is to do nothing. + """ + @spec refresh_cluster(Store.t()) :: :ok + def refresh_cluster(_config), do: :ok + + @doc ~S""" + Called by mnesiac when it has detected records for a store on both the local and remote nodes it is connecting to. + + **Note**: The default implementation is to do nothing. + """ + @spec resolve_conflict(Store.t(), node()) :: :ok + def resolve_conflict(_config, _cluster_node), do: :ok + + defp maybe_apply_schema(type, cluster_node, nodes) do + if Enum.member?(nodes, node()) do + case :mnesia.change_table_copy_type(:schema, cluster_node, type) do + {:atomic, :ok} -> :ok + {:aborted, {:already_exists, :schema, _, _}} -> :ok + {:aborted, reason} -> {:error, reason} end + end + end + + defmacro __using__(_) do + quote do + @behaviour Mnesiac.Store + + defdelegate init_schema(config), to: Mnesiac.Store + defdelegate copy_schema(config, cluster_node), to: Mnesiac.Store + defdelegate init_store(config), to: Mnesiac.Store + defdelegate copy_store(config), to: Mnesiac.Store + defdelegate refresh_cluster(config), to: Mnesiac.Store + defdelegate resolve_conflict(config, cluster_node), to: Mnesiac.Store defoverridable Mnesiac.Store end diff --git a/lib/mnesiac/store_manager.ex b/lib/mnesiac/store_manager.ex deleted file mode 100644 index 5b29746..0000000 --- a/lib/mnesiac/store_manager.ex +++ /dev/null @@ -1,134 +0,0 @@ -defmodule Mnesiac.StoreManager do - @moduledoc """ - Mnesia Store Manager - """ - require Logger - - @doc """ - Init tables - """ - def init_tables do - case :mnesia.system_info(:extra_db_nodes) do - [head | _tail] -> - copy_tables(head) - - [] -> - create_tables() - end - end - - @doc """ - Ensure tables loaded - """ - def ensure_tables_loaded do - tables = :mnesia.system_info(:local_tables) - - case :mnesia.wait_for_tables(tables, table_load_timeout()) do - :ok -> - :ok - - {:error, reason} -> - {:error, reason} - - {:timeout, bad_tables} -> - {:error, {:timeout, bad_tables}} - end - end - - @doc """ - Create tables - """ - def create_tables do - Enum.each(stores(), fn data_mapper -> - apply(data_mapper, :init_store, []) - end) - - :ok - end - - @doc """ - Copy tables - """ - def copy_tables(cluster_node) do - local_cookies = get_table_cookies() - remote_cookies = get_table_cookies(cluster_node) - - Enum.each(stores(), fn data_mapper -> - cookie = Keyword.get(data_mapper.store_options(), :record_name, data_mapper) - - case {local_cookies[cookie], remote_cookies[cookie]} do - {nil, nil} -> - apply(data_mapper, :init_store, []) - - {nil, _} -> - apply(data_mapper, :copy_store, []) - - {_, nil} -> - Logger.info("[mnesiac:#{node()}] #{inspect(data_mapper)}: no remote data to copy found.") - {:error, :no_remote_data_to_copy} - - {_local, _remote} -> - apply(data_mapper, :resolve_conflict, [cluster_node]) - end - end) - - :ok - end - - @doc """ - Copy schema - """ - def copy_schema(cluster_node) do - copy_type = Application.get_env(:mnesiac, :schema_type, :ram_copies) - - case :mnesia.change_table_copy_type(:schema, cluster_node, copy_type) do - {:atomic, :ok} -> - :ok - - {:aborted, {:already_exists, :schema, _, _}} -> - :ok - - {:aborted, reason} -> - {:error, reason} - end - end - - @doc """ - Delete schema - """ - def delete_schema do - :mnesia.delete_schema([node()]) - end - - @doc """ - Delete schema copy - """ - def del_schema_copy(cluster_node) do - case :mnesia.del_table_copy(:schema, cluster_node) do - {:atomic, :ok} -> - :ok - - {:aborted, reason} -> - {:error, reason} - end - end - - defp stores do - Application.get_env(:mnesiac, :stores) - end - - defp table_load_timeout do - Application.get_env(:mnesiac, :table_load_timeout, 600_000) - end - - @doc """ - This function returns a map of tables and their cookies. - """ - def get_table_cookies(node \\ node()) do - tables = :rpc.call(node, :mnesia, :system_info, [:tables]) - - Enum.reduce(tables, %{}, fn t, acc -> - Map.put(acc, t, :rpc.call(node, :mnesia, :table_info, [t, :cookie])) - end) - end -end diff --git a/lib/mnesiac/supervisor.ex b/lib/mnesiac/supervisor.ex index 593cf35..e038c14 100644 --- a/lib/mnesiac/supervisor.ex +++ b/lib/mnesiac/supervisor.ex @@ -1,21 +1,52 @@ defmodule Mnesiac.Supervisor do - @moduledoc false - require Logger + @moduledoc """ + Mnesiac supervisor. + """ use Supervisor - def start_link([_config, opts] = args) do - Supervisor.start_link(__MODULE__, args, opts) + @doc """ + Entry point for Mnesiac when used in a supervision tree. + + ## Example + + ```elixir + config = [ + schema: [ + disc_copies: [:n3@local, :n4@local, :n6@local], + ram_copies: [:n10@local, :n11@local] + ], + stores: [ + [ + ref: Mnesiac.ExampleStore, + disc_copies: [:n3@local, :n4@local, :n6@local], + ram_copies: [:n10@local, :n11@local], + blacklist: [:n10@local, :n11@local] + ], + [ + ref: Mnesiac.ExampleStoreTwo, + disc_copies: [:n10@local, :n11@local], + ram_copies: [:n3@local, :n4@local, :n6@local] + ] + ], + store_load_timeout: 600_000 + ] + + Mnesiac.Supervisor.start_link([cluster: [node()], config: config]) + ``` + """ + @spec start_link(init_arg :: [Mnesiac.init_arg() | keyword()] | Mnesiac.init_arg()) :: + :ignore | {:error, term()} | {:ok, pid()} + def start_link([[cluster: _cluster, config: _config], opts] = init_arg) do + Supervisor.start_link(__MODULE__, init_arg, opts) end - def start_link([config]) do - start_link([config, []]) + def start_link([cluster: _cluster, config: _config] = init_arg) do + start_link([init_arg, []]) end @impl true def init([config, opts]) do - Logger.info("[mnesiac:#{node()}] mnesiac starting...") Mnesiac.init_mnesia(config) - Logger.info("[mnesiac:#{node()}] mnesiac started") opts = Keyword.put(opts, :strategy, :one_for_one) Supervisor.init([], opts) diff --git a/mix.exs b/mix.exs index 75407c1..1a2a5eb 100644 --- a/mix.exs +++ b/mix.exs @@ -1,13 +1,12 @@ defmodule Mnesiac.MixProject do @moduledoc false - require Logger use Mix.Project def project do [ app: :mnesiac, - version: "0.3.9", - elixir: "~> 1.8", + version: "0.4.0", + elixir: "~> 1.11", elixirc_paths: elixirc_paths(Mix.env()), test_coverage: [tool: ExCoveralls], preferred_cli_env: [ @@ -47,9 +46,9 @@ defmodule Mnesiac.MixProject do "format --check-formatted --dry-run", "compile --warning-as-errors --force", "credo --strict --all", - "inch" + "doctor" ], - "purge.db": &purge_db/1 + "db.purge": &purge_db/1 ], name: "Mnesiac", source_url: "https://github.com/beardedeagle/mnesiac", @@ -69,13 +68,15 @@ defmodule Mnesiac.MixProject do defp deps do [ - {:libcluster, "~> 3.2", optional: true}, - {:credo, "~> 1.5", only: [:dev], runtime: false}, + {:libcluster, "~> 3.3", optional: true}, + {:credo, "~> 1.6", only: [:dev], runtime: false}, {:dialyxir, "~> 1.1", only: [:dev], runtime: false}, - {:ex_doc, "~> 0.23", only: [:dev], runtime: false}, - {:ex_unit_clustered_case, "~> 0.4", only: [:test]}, + {:doctor, "~> 0.18", only: [:dev], runtime: false}, + {:ex_check, "~> 0.14", only: [:dev], runtime: false}, + {:ex_doc, "~> 0.26", only: [:dev], runtime: false}, {:excoveralls, "~> 0.14", only: [:test], runtime: false}, - {:inch_ex, "~> 2.0", only: [:dev], runtime: false} + {:local_cluster, "~> 1.2", only: [:test]}, + {:mix_audit, "~> 1.0", only: [:dev], runtime: false} ] end @@ -83,7 +84,7 @@ defmodule Mnesiac.MixProject do if Mix.env() in [:dev, :test] do Mix.shell().cmd("rm -rf ./test0* ./Mnesia.nonode@nohost") else - Logger.info("[mnesiac:#{node()}] purge.db can only be used in dev and test env") + Mix.shell().info("[mnesiac:#{node()}] purge.db can only be used in dev and test env") end end end diff --git a/mix.lock b/mix.lock index afa1081..8d4479d 100644 --- a/mix.lock +++ b/mix.lock @@ -1,25 +1,32 @@ %{ "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], [], "hexpm", "7af5c7e09fe1d40f76c8e4f9dd2be7cebd83909f31fee7cd0e9eadc567da8353"}, - "certifi": {:hex, :certifi, "2.5.3", "70bdd7e7188c804f3a30ee0e7c99655bc35d8ac41c23e12325f36ab449b70651", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm", "ed516acb3929b101208a9d700062d520f3953da3b6b918d866106ffa980e1c10"}, - "credo": {:hex, :credo, "1.5.5", "e8f422026f553bc3bebb81c8e8bf1932f498ca03339856c7fec63d3faac8424b", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dd8623ab7091956a855dc9f3062486add9c52d310dfd62748779c4315d8247de"}, + "certifi": {:hex, :certifi, "2.8.0", "d4fb0a6bb20b7c9c3643e22507e42f356ac090a1dcea9ab99e27e0376d695eba", [:rebar3], [], "hexpm", "6ac7efc1c6f8600b08d625292d4bbf584e14847ce1b6b5c44d983d273e1097ea"}, + "credo": {:hex, :credo, "1.6.1", "7dc76dcdb764a4316c1596804c48eada9fff44bd4b733a91ccbf0c0f368be61e", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "698607fb5993720c7e93d2d8e76f2175bba024de964e160e2f7151ef3ab82ac5"}, + "decimal": {:hex, :decimal, "2.0.0", "a78296e617b0f5dd4c6caf57c714431347912ffb1d0842e998e9792b5642d697", [:mix], [], "hexpm", "34666e9c55dea81013e77d9d87370fe6cb6291d1ef32f46a1600230b1d44f577"}, "dialyxir": {:hex, :dialyxir, "1.1.0", "c5aab0d6e71e5522e77beff7ba9e08f8e02bad90dfbeffae60eaf0cb47e29488", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "07ea8e49c45f15264ebe6d5b93799d4dd56a44036cf42d0ad9c960bc266c0b9a"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.12", "b245e875ec0a311a342320da0551da407d9d2b65d98f7a9597ae078615af3449", [:mix], [], "hexpm", "711e2cc4d64abb7d566d43f54b78f7dc129308a63bc103fbd88550d2174b3160"}, + "doctor": {:hex, :doctor, "0.18.0", "114934c1740239953208a39db617699b7e2660770e81129d7f95cdf7837ab766", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "829c88c365f72c0666e443ea670ffb6f180de7b90c23d536edabdd8c722b88f4"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.18", "e1b2be73eb08a49fb032a0208bf647380682374a725dfb5b9e510def8397f6f2", [:mix], [], "hexpm", "114a0e85ec3cf9e04b811009e73c206394ffecfcc313e0b346de0d557774ee97"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, - "ex_doc": {:hex, :ex_doc, "0.23.0", "a069bc9b0bf8efe323ecde8c0d62afc13d308b1fa3d228b65bca5cf8703a529d", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}], "hexpm", "f5e2c4702468b2fd11b10d39416ddadd2fcdd173ba2a0285ebd92c39827a5a16"}, - "ex_unit_clustered_case": {:hex, :ex_unit_clustered_case, "0.4.0", "8ab43bee163f929cee471d7198d561d32eab2dc2185e822b9d76c812ad1089d6", [:mix], [], "hexpm", "d7df6421a28fc8c4312d6ffda920d3d6ee06685916bb487573e05443b295f5d0"}, - "excoveralls": {:hex, :excoveralls, "0.14.0", "4b562d2acd87def01a3d1621e40037fdbf99f495ed3a8570dfcf1ab24e15f76d", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "94f17478b0cca020bcd85ce7eafea82d2856f7ed022be777734a2f864d36091a"}, + "ex_check": {:hex, :ex_check, "0.14.0", "d6fbe0bcc51cf38fea276f5bc2af0c9ae0a2bb059f602f8de88709421dae4f0e", [:mix], [], "hexpm", "8a602e98c66e6a4be3a639321f1f545292042f290f91fa942a285888c6868af0"}, + "ex_doc": {:hex, :ex_doc, "0.26.0", "1922164bac0b18b02f84d6f69cab1b93bc3e870e2ad18d5dacb50a9e06b542a3", [:mix], [{:earmark_parser, "~> 1.4.0", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2775d66e494a9a48355db7867478ffd997864c61c65a47d31c4949459281c78d"}, + "excoveralls": {:hex, :excoveralls, "0.14.4", "295498f1ae47bdc6dce59af9a585c381e1aefc63298d48172efaaa90c3d251db", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e3ab02f2df4c1c7a519728a6f0a747e71d7d6e846020aae338173619217931c1"}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, - "hackney": {:hex, :hackney, "1.17.0", "717ea195fd2f898d9fe9f1ce0afcc2621a41ecfe137fae57e7fe6e9484b9aa99", [:rebar3], [{:certifi, "~>2.5", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "64c22225f1ea8855f584720c0e5b3cd14095703af1c9fbc845ba042811dc671c"}, + "global_flags": {:hex, :global_flags, "1.0.0", "ee6b864979a1fb38d1fbc67838565644baf632212bce864adca21042df036433", [:rebar3], [], "hexpm", "85d944cecd0f8f96b20ce70b5b16ebccedfcd25e744376b131e89ce61ba93176"}, + "hackney": {:hex, :hackney, "1.18.0", "c4443d960bb9fba6d01161d01cd81173089686717d9490e5d3606644c48d121f", [:rebar3], [{:certifi, "~>2.8.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "9afcda620704d720db8c6a3123e9848d09c87586dc1c10479c42627b905b5c5e"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, - "inch_ex": {:hex, :inch_ex, "2.0.0", "24268a9284a1751f2ceda569cd978e1fa394c977c45c331bb52a405de544f4de", [:mix], [{:bunt, "~> 0.2", [hex: :bunt, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "96d0ec5ecac8cf63142d02f16b7ab7152cf0f0f1a185a80161b758383c9399a8"}, "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, - "libcluster": {:hex, :libcluster, "3.2.2", "8d98f7492c315fa0374acd405b7cb04205e737c3e245c8d5ff2503c0420bd6b3", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "3f1c97e0d5b439476b4ea7b74fb4d0e70eb770b6a8503c2e4964906723a83dd3"}, + "libcluster": {:hex, :libcluster, "3.3.0", "f7d45ff56d88e9fb4c30aee662480cbab69ebc0e7f7da4ad8d01b1e4f7492da8", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "ecdcdc88334ec8eb18b10a13a1d5f22a3319a970b5b1e66cfe71c7719a4ab6cc"}, + "local_cluster": {:hex, :local_cluster, "1.2.1", "8eab3b8a387680f0872eacfb1a8bd5a91cb1d4d61256eec6a655b07ac7030c73", [:mix], [{:global_flags, "~> 1.0", [hex: :global_flags, repo: "hexpm", optional: false]}], "hexpm", "aae80c9bc92c911cb0be085fdeea2a9f5b88f81b6bec2ff1fec244bb0acc232c"}, "makeup": {:hex, :makeup, "1.0.5", "d5a830bc42c9800ce07dd97fa94669dfb93d3bf5fcf6ea7a0c67b2e0e4a7f26c", [:mix], [{:nimble_parsec, "~> 0.5 or ~> 1.0", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cfa158c02d3f5c0c665d0af11512fed3fba0144cf1aadee0f2ce17747fba2ca9"}, - "makeup_elixir": {:hex, :makeup_elixir, "0.15.1", "b5888c880d17d1cc3e598f05cdb5b5a91b7b17ac4eaf5f297cb697663a1094dd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "db68c173234b07ab2a07f645a5acdc117b9f99d69ebf521821d89690ae6c6ec8"}, + "makeup_elixir": {:hex, :makeup_elixir, "0.15.2", "dc72dfe17eb240552857465cc00cce390960d9a0c055c4ccd38b70629227e97c", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.1", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "fd23ae48d09b32eff49d4ced2b43c9f086d402ee4fd4fcb2d7fad97fa8823e75"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.1.0", "3a6fca1550363552e54c216debb6a9e95bd8d32348938e13de5eda962c0d7f89", [:mix], [], "hexpm", "08eb32d66b706e913ff748f11694b17981c0b04a33ef470e33e11b3d3ac8f54b"}, + "mix_audit": {:hex, :mix_audit, "1.0.0", "d2b5adbd69f34ba6b5b7d52812b1ba06f9110367e196d3ba5dba7753124cf8be", [:make, :mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:yaml_elixir, "~> 2.8.0", [hex: :yaml_elixir, repo: "hexpm", optional: false]}], "hexpm", "1b1ff6694e6eb12818ce5dcc276a39bbe03e27fcd11376c381bfe6b4900f2aa8"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.2.0", "b44d75e2a6542dcb6acf5d71c32c74ca88960421b6874777f79153bbbbd7dccc", [:mix], [], "hexpm", "52b2871a7515a5ac49b00f214e4165a40724cf99798d8e4a65e4fd64ebd002c1"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, + "yamerl": {:hex, :yamerl, "0.8.1", "07da13ffa1d8e13948943789665c62ccd679dfa7b324a4a2ed3149df17f453a4", [:rebar3], [], "hexpm", "96cb30f9d64344fed0ef8a92e9f16f207de6c04dfff4f366752ca79f5bceb23f"}, + "yaml_elixir": {:hex, :yaml_elixir, "2.8.0", "c7ff0034daf57279c2ce902788ce6fdb2445532eb4317e8df4b044209fae6832", [:mix], [{:yamerl, "~> 0.8", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "4b674bd881e373d1ac6a790c64b2ecb69d1fd612c2af3b22de1619c15473830b"}, } diff --git a/test/support/example_store.ex b/test/support/example_store_one.ex similarity index 50% rename from test/support/example_store.ex rename to test/support/example_store_one.ex index e7da043..2e6308d 100644 --- a/test/support/example_store.ex +++ b/test/support/example_store_one.ex @@ -1,19 +1,19 @@ -defmodule Mnesiac.Support.ExampleStore do +defmodule Mnesiac.Support.ExampleStoreOne do @moduledoc false - require Record use Mnesiac.Store + import Record, only: [defrecord: 3] - Record.defrecord( - :example, - __MODULE__, + defrecord( + :exampleone, + ExampleStoreOne, id: nil, topic_id: nil, event: nil ) - @type example :: + @type exampleone :: record( - :example, + :exampleone, id: String.t(), topic_id: String.t(), event: String.t() @@ -22,13 +22,9 @@ defmodule Mnesiac.Support.ExampleStore do @impl true def store_options, do: [ - attributes: example() |> example() |> Keyword.keys(), + record_name: ExampleStoreOne, + attributes: exampleone() |> exampleone() |> Keyword.keys(), index: [:topic_id], disc_copies: [node()] ] - - @impl true - def copy_store do - :mnesia.add_table_copy(__MODULE__, node(), :disc_copies) - end end diff --git a/test/support/example_store_two.ex b/test/support/example_store_two.ex new file mode 100644 index 0000000..645c0d9 --- /dev/null +++ b/test/support/example_store_two.ex @@ -0,0 +1,30 @@ +defmodule Mnesiac.Support.ExampleStoreTwo do + @moduledoc false + use Mnesiac.Store + import Record, only: [defrecord: 3] + + defrecord( + :exampletwo, + ExampleStoreTwo, + id: nil, + topic_id: nil, + event: nil + ) + + @type exampletwo :: + record( + :exampletwo, + id: String.t(), + topic_id: String.t(), + event: String.t() + ) + + @impl true + def store_options, + do: [ + record_name: ExampleStoreTwo, + attributes: exampletwo() |> exampletwo() |> Keyword.keys(), + index: [:topic_id], + disc_copies: [node()] + ] +end