From 071fd228f3a4cba9a985091d18eb01d90f6670ab Mon Sep 17 00:00:00 2001 From: jaeyson Date: Thu, 4 Jul 2024 01:21:57 +0800 Subject: [PATCH 01/11] Clear all documents without dropping schema --- lib/ex_typesense.ex | 10 ++++++++++ lib/ex_typesense/document.ex | 14 ++++++++++++++ test/document_test.exs | 4 ++++ 3 files changed, 28 insertions(+) diff --git a/lib/ex_typesense.ex b/lib/ex_typesense.ex index 444b56f..fb243a5 100644 --- a/lib/ex_typesense.ex +++ b/lib/ex_typesense.ex @@ -87,8 +87,18 @@ defmodule ExTypesense do to: ExTypesense.Document defdelegate create_document(conn \\ Connection.new(), document), to: ExTypesense.Document + # TODO: pass optional conn defdelegate delete_document(document), to: ExTypesense.Document + # TODO: pass optional conn defdelegate delete_document(collection_name, document_id), to: ExTypesense.Document + + defdelegate delete_all_documents( + conn \\ Connection.new(), + module_or_collection_name, + query \\ %{} + ), + to: ExTypesense.Document + defdelegate update_document(conn \\ Connection.new(), document), to: ExTypesense.Document defdelegate upsert_document(conn \\ Connection.new(), document), to: ExTypesense.Document diff --git a/lib/ex_typesense/document.ex b/lib/ex_typesense/document.ex index 268acbf..3f8b5eb 100644 --- a/lib/ex_typesense/document.ex +++ b/lib/ex_typesense/document.ex @@ -375,6 +375,7 @@ defmodule ExTypesense.Document do Deletes a document by struct. """ @doc since: "0.3.0" + # TODO: pass optional conn @spec delete_document(struct()) :: response() def delete_document(struct) when is_struct(struct) do document_id = struct.id @@ -412,12 +413,25 @@ defmodule ExTypesense.Document do } """ @doc since: "0.3.0" + # TODO: pass optional conn @spec delete_document(String.t(), integer()) :: response() def delete_document(collection_name, document_id) when is_binary(collection_name) and is_integer(document_id) do do_delete_document(collection_name, document_id) end + @doc since: "0.5.0" + @spec delete_all_documents(Connection.t(), module() | String.t(), map()) :: response() + def delete_all_documents(conn \\ Connection.new(), module_or_collection_name, query \\ %{}) + + def delete_all_documents(conn, collection_name, query) when is_binary(collection_name) do + %{error: %{message: "not implemented yet"}} + end + + def delete_all_documents(conn, module_name, query) when is_atom(module_name) do + %{error: %{message: "not implemented yet"}} + end + @deprecated "use do_delete_document/3" defp do_delete_document(collection_name, document_id) do path = diff --git a/test/document_test.exs b/test/document_test.exs index ef63716..49c409c 100644 --- a/test/document_test.exs +++ b/test/document_test.exs @@ -191,4 +191,8 @@ defmodule DocumentTest do assert {:error, ~s(It should be type of map, ":documents" key should contain list of maps)} === ExTypesense.upsert_multiple_documents(persons) end + + test "success: delete all documents in a collection", %{schema: schema} do + assert %{ok: %{}} == ExTypesense.delete_all_documents(schema.name) + end end From b13231a6801ff1b23739edb8c50d8febe07a2d0a Mon Sep 17 00:00:00 2001 From: jaeyson Date: Mon, 8 Jul 2024 14:38:00 +0800 Subject: [PATCH 02/11] fix parser to point on default_sorting_field --- .credo.exs | 217 ++++++++++++++++++++++++ .iex.exs | 1 + README.md | 6 + lib/ex_typesense.ex | 18 +- lib/ex_typesense/collection.ex | 20 ++- lib/ex_typesense/document.ex | 168 ++++++++++++++---- lib/ex_typesense/http_client.ex | 111 ------------ lib/ex_typesense/result_parser.ex | 28 ++- lib/ex_typesense/search.ex | 12 +- lib/ex_typesense/test_schema/catalog.ex | 12 +- lib/ex_typesense/test_schema/person.ex | 12 +- test/document_test.exs | 77 ++++++--- test/search_test.exs | 10 +- 13 files changed, 465 insertions(+), 227 deletions(-) create mode 100644 .credo.exs diff --git a/.credo.exs b/.credo.exs new file mode 100644 index 0000000..784f814 --- /dev/null +++ b/.credo.exs @@ -0,0 +1,217 @@ +# This file contains the configuration for Credo and you are probably reading +# this after creating it with `mix credo.gen.config`. +# +# If you find anything wrong or unclear in this file, please report an +# issue on GitHub: https://github.com/rrrene/credo/issues +# +%{ + # + # You can have as many configs as you like in the `configs:` field. + configs: [ + %{ + # + # Run any config using `mix credo -C `. If no config name is given + # "default" is used. + # + name: "default", + # + # These are the files included in the analysis: + files: %{ + # + # You can give explicit globs or simply directories. + # In the latter case `**/*.{ex,exs}` will be used. + # + included: [ + "lib/", + "src/", + "test/", + "web/", + "apps/*/lib/", + "apps/*/src/", + "apps/*/test/", + "apps/*/web/" + ], + excluded: [~r"/_build/", ~r"/deps/", ~r"/node_modules/"] + }, + # + # Load and configure plugins here: + # + plugins: [], + # + # If you create your own checks, you must specify the source files for + # them here, so they can be loaded by Credo before running the analysis. + # + requires: [], + # + # If you want to enforce a style guide and need a more traditional linting + # experience, you can change `strict` to `true` below: + # + strict: false, + # + # To modify the timeout for parsing files, change this value: + # + parse_timeout: 5000, + # + # If you want to use uncolored output by default, you can change `color` + # to `false` below: + # + color: true, + # + # You can customize the parameters of any check by adding a second element + # to the tuple. + # + # To disable a check put `false` as second element: + # + # {Credo.Check.Design.DuplicatedCode, false} + # + checks: %{ + enabled: [ + # + ## Consistency 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, []}, + + # + ## Design Checks + # + # You can customize the priority of any check + # Priority values are: `low, normal, high, higher` + # + {Credo.Check.Design.AliasUsage, + [priority: :low, if_nested_deeper_than: 2, if_called_more_often_than: 0]}, + {Credo.Check.Design.TagFIXME, []}, + # You can also customize the exit_status of each check. + # If you don't want TODO comments to cause `mix credo` to fail, just + # set this value to 0 (zero). + # + {Credo.Check.Design.TagTODO, [exit_status: 2]}, + + # + ## Readability Checks + # + {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, []}, + + # + ## Refactoring Opportunities + # + {Credo.Check.Refactor.Apply, []}, + {Credo.Check.Refactor.CondStatements, []}, + {Credo.Check.Refactor.CyclomaticComplexity, []}, + {Credo.Check.Refactor.FilterCount, []}, + {Credo.Check.Refactor.FilterFilter, []}, + {Credo.Check.Refactor.FunctionArity, []}, + {Credo.Check.Refactor.LongQuoteBlocks, []}, + {Credo.Check.Refactor.MapJoin, []}, + {Credo.Check.Refactor.MatchInCondition, []}, + {Credo.Check.Refactor.NegatedConditionsInUnless, []}, + {Credo.Check.Refactor.NegatedConditionsWithElse, []}, + {Credo.Check.Refactor.Nesting, [max_nesting: 3]}, + {Credo.Check.Refactor.RedundantWithClauseResult, []}, + {Credo.Check.Refactor.RejectReject, []}, + {Credo.Check.Refactor.UnlessWithElse, []}, + {Credo.Check.Refactor.WithClauses, []}, + + # + ## Warnings + # + {Credo.Check.Warning.ApplicationConfigInModuleAttribute, []}, + {Credo.Check.Warning.BoolOperationOnSameValues, []}, + {Credo.Check.Warning.Dbg, []}, + {Credo.Check.Warning.ExpensiveEmptyEnumCheck, []}, + {Credo.Check.Warning.IExPry, []}, + {Credo.Check.Warning.IoInspect, []}, + {Credo.Check.Warning.MissedMetadataKeyInLoggerConfig, []}, + {Credo.Check.Warning.OperationOnSameValues, []}, + {Credo.Check.Warning.OperationWithConstantResult, []}, + {Credo.Check.Warning.RaiseInsideRescue, []}, + {Credo.Check.Warning.SpecWithStruct, []}, + {Credo.Check.Warning.UnsafeExec, []}, + {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.WrongTestFileExtension, []} + ], + disabled: [ + # + # Checks scheduled for next check update (opt-in for now) + {Credo.Check.Refactor.UtcNowTruncate, []}, + + # + # Controversial and experimental checks (opt-in, just move the check to `:enabled` + # and be sure to use `mix credo --strict` to see low priority checks) + # + {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.NestedFunctionCalls, []}, + {Credo.Check.Readability.OneArityFunctionInPipe, []}, + {Credo.Check.Readability.OnePipePerLine, []}, + {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.PassAsyncInTestCases, []}, + {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, []} + + # {Credo.Check.Refactor.MapInto, []}, + + # + # Custom checks can be created using `mix credo.gen.check`. + # + ] + } + } + ] +} diff --git a/.iex.exs b/.iex.exs index 115c75d..1ab90ce 100644 --- a/.iex.exs +++ b/.iex.exs @@ -6,3 +6,4 @@ alias ExTypesense.HttpClient alias ExTypesense.Search alias ExTypesense.TestSchema.Credential alias ExTypesense.TestSchema.Person +alias ExTypesense.TestSchema.Catalog diff --git a/README.md b/README.md index 876fc75..231fcf8 100644 --- a/README.md +++ b/README.md @@ -105,6 +105,8 @@ conn = %{ scheme: "https" } +# NOTE: create a collection and import documents +# first before using the command below ExTypesense.search(conn, collection_name, query) ``` @@ -113,6 +115,8 @@ Or convert your struct to map, as long as the keys matches in `ExTypesense.Conne ```elixir conn = Map.from_struct(MyApp.Credential) +# NOTE: create a collection and import documents +# first before using the command below ExTypesense.search(conn, collection_name, query) ``` @@ -134,6 +138,8 @@ conn = |> Map.put(:host, conn.node) |> Map.put(:api_key, conn.secret_key) +# NOTE: create a collection and import documents +# first before using the command below ExTypesense.search(conn, collection_name, query) ``` diff --git a/lib/ex_typesense.ex b/lib/ex_typesense.ex index fb243a5..c6976eb 100644 --- a/lib/ex_typesense.ex +++ b/lib/ex_typesense.ex @@ -87,17 +87,11 @@ defmodule ExTypesense do to: ExTypesense.Document defdelegate create_document(conn \\ Connection.new(), document), to: ExTypesense.Document - # TODO: pass optional conn - defdelegate delete_document(document), to: ExTypesense.Document - # TODO: pass optional conn - defdelegate delete_document(collection_name, document_id), to: ExTypesense.Document - - defdelegate delete_all_documents( - conn \\ Connection.new(), - module_or_collection_name, - query \\ %{} - ), - to: ExTypesense.Document + defdelegate delete_document(conn \\ Connection.new(), document), to: ExTypesense.Document + defdelegate delete_documents_by_query(conn \\ Connection.new(), query), to: ExTypesense.Document + + defdelegate delete_all_documents(conn \\ Connection.new(), collection_name), + to: ExTypesense.Document defdelegate update_document(conn \\ Connection.new(), document), to: ExTypesense.Document defdelegate upsert_document(conn \\ Connection.new(), document), to: ExTypesense.Document @@ -112,7 +106,7 @@ defmodule ExTypesense do to: ExTypesense.Document # search - defdelegate search(conn \\ Connection.new(), module_or_collection_name, params), + defdelegate search(conn \\ Connection.new(), collection_name, params), to: ExTypesense.Search # geo search diff --git a/lib/ex_typesense/collection.ex b/lib/ex_typesense/collection.ex index 9abcd34..204063e 100644 --- a/lib/ex_typesense/collection.ex +++ b/lib/ex_typesense/collection.ex @@ -26,7 +26,11 @@ defmodule ExTypesense.Collection do :optional, :sort, :type, - :vec_dist + :vec_dist, + :store, + :reference, + :range_index, + :stem ] @typedoc since: "0.1.0" @@ -42,7 +46,11 @@ defmodule ExTypesense.Collection do optional: boolean(), sort: boolean(), type: field_type(), - vec_dist: String.t() + vec_dist: String.t(), + store: boolean(), + reference: String.t(), + range_index: boolean(), + stem: boolean() } @typedoc since: "0.1.0" @@ -62,6 +70,8 @@ defmodule ExTypesense.Collection do | :object | :"object[]" | :"string*" + | :image + | :auto end @collections_path "/collections" @@ -433,14 +443,12 @@ defmodule ExTypesense.Collection do collection = Map.new(collection, fn {k, v} -> if k === :fields do - Map.new(v, &to_atom/1) + Map.new(v, fn {k, v} -> {String.to_existing_atom(k), v} end) else - {String.to_atom(k), v} + {String.to_existing_atom(k), v} end end) struct(__MODULE__, collection) end - - defp to_atom({k, v}), do: {String.to_atom(k), v} end diff --git a/lib/ex_typesense/document.ex b/lib/ex_typesense/document.ex index 3f8b5eb..7d89b33 100644 --- a/lib/ex_typesense/document.ex +++ b/lib/ex_typesense/document.ex @@ -44,11 +44,11 @@ defmodule ExTypesense.Document do """ @doc since: "0.1.0" @spec get_document(Connection.t(), module() | String.t(), integer()) :: response() - def get_document(conn \\ Connection.new(), module_name, document_id) + def get_document(conn \\ Connection.new(), collection_name, document_id) - def get_document(conn, module_name, document_id) - when is_atom(module_name) and is_integer(document_id) do - collection_name = module_name.__schema__(:source) + def get_document(conn, collection_name, document_id) + when is_atom(collection_name) and is_integer(document_id) do + collection_name = collection_name.__schema__(:source) do_get_document(conn, collection_name, document_id) end @@ -372,21 +372,26 @@ defmodule ExTypesense.Document do end @doc """ - Deletes a document by struct. - """ - @doc since: "0.3.0" - # TODO: pass optional conn - @spec delete_document(struct()) :: response() - def delete_document(struct) when is_struct(struct) do - document_id = struct.id - collection_name = struct.__struct__.__schema__(:source) - do_delete_document(collection_name, document_id) - end + Deletes a document by id or struct. - @doc """ - Deletes a document by id. + > #### Deleting a document by id {: .info} + > + > If you are deleting by id, pass it as a tuple (`{"collection_name", 23}`) ## Examples + iex> ExTypesense.create_collection(Post) + iex> post = Post |> limit(1) |> Repo.one() + iex> ExTypesense.create_collection(post) + iex> ExTypesense.delete_document(post) + {:ok, + %{ + "id" => "1", + "post_id" => 1, + "title" => "our first post", + "collection_name" => "posts" + } + } + iex> schema = %{ ...> name: "posts", ...> fields: [ @@ -402,7 +407,7 @@ defmodule ExTypesense.Document do ...> title: "the quick brown fox" ...> } iex> ExTypesense.create_document(post) - iex> ExTypesense.delete_document("posts", 12) + iex> ExTypesense.delete_document({"posts", 12}) {:ok, %{ "id" => "12", @@ -413,27 +418,32 @@ defmodule ExTypesense.Document do } """ @doc since: "0.3.0" - # TODO: pass optional conn - @spec delete_document(String.t(), integer()) :: response() - def delete_document(collection_name, document_id) - when is_binary(collection_name) and is_integer(document_id) do - do_delete_document(collection_name, document_id) - end - - @doc since: "0.5.0" - @spec delete_all_documents(Connection.t(), module() | String.t(), map()) :: response() - def delete_all_documents(conn \\ Connection.new(), module_or_collection_name, query \\ %{}) + @spec delete_document(Connection.t(), struct() | {String.t(), integer()}) :: response() + def delete_document(conn \\ Connection.new(), struct_or_tuple) - def delete_all_documents(conn, collection_name, query) when is_binary(collection_name) do - %{error: %{message: "not implemented yet"}} + def delete_document(conn, struct) when is_struct(struct) do + # document_id = struct.id + collection_name = struct.__struct__.__schema__(:source) + # delete_document(conn, {collection_name, document_id}) + filter_by = + :virtual_fields + |> struct.__struct__.__schema__() + |> Enum.filter(&String.contains?(to_string(&1), "_id")) + |> Enum.map_join(" || ", fn virtual_field -> + value = Map.get(struct, virtual_field) + "#{virtual_field}:#{value}" + end) + + delete_documents_by_query(conn, collection_name, %{filter_by: filter_by}) end - def delete_all_documents(conn, module_name, query) when is_atom(module_name) do - %{error: %{message: "not implemented yet"}} + def delete_document(conn, {collection_name, document_id} = _tuple) + when is_binary(collection_name) and is_integer(document_id) do + do_delete_document(conn, collection_name, document_id) end - @deprecated "use do_delete_document/3" - defp do_delete_document(collection_name, document_id) do + @spec do_delete_document(Connection.t(), String.t(), integer()) :: response() + defp do_delete_document(conn, collection_name, document_id) do path = Path.join([ @collections_path, @@ -442,6 +452,96 @@ defmodule ExTypesense.Document do Jason.encode!(document_id) ]) - HttpClient.run(:delete, path) + opts = %{ + method: :delete, + path: path + } + + HttpClient.request(conn, opts) + end + + @doc """ + Deletes documents in a collection by query. + + > #### [Filter and batch size](https://typesense.org/docs/latest/api/documents.html#delete-by-query) {: .info} + > + > To delete all documents in a collection, you can use a filter that + > matches all documents in your collection. For eg, if you have an + > int32 field called popularity in your documents, you can use + > `filter_by: "popularity:>0"` to delete all documents. Or if you have a + > bool field called `in_stock` in your documents, you can use + > `filter_by: "in_stock:[true,false]"` to delete all documents. + > + > Use the `batch_size` to control the number of documents that should + > deleted at a time. A larger value will speed up deletions, but will + > impact performance of other operations running on the server. + > + > Filter parameters can be found here: https://typesense.org/docs/latest/api/search.html#filter-parameters + + ## Examples + iex> query = %{ + ...> filter_by: "num_employees:>100", + ...> batch_size: 100 + ...> } + iex> ExTypesense.delete_documents_by_query(query) + {:ok, %{}} + """ + @doc since: "0.5.0" + @spec delete_documents_by_query( + Connection.t(), + module() | String.t(), + %{ + filter_by: String.t(), + batch_size: integer() | nil + } + ) :: + response() + def delete_documents_by_query(conn \\ Connection.new(), collection_name, query) + + def delete_documents_by_query(conn, collection_name, %{filter_by: filter_by} = query) + when not is_nil(filter_by) and is_binary(filter_by) and is_atom(collection_name) do + path = Path.join([@collections_path, collection_name, @documents_path]) + HttpClient.request(conn, %{method: :delete, path: path, query: query}) + end + + def delete_documents_by_query(conn, collection_name, %{filter_by: filter_by} = query) + when not is_nil(filter_by) and is_binary(filter_by) and is_binary(collection_name) do + path = Path.join([@collections_path, collection_name, @documents_path]) + HttpClient.request(conn, %{method: :delete, path: path, query: query}) end + + @doc """ + Deletes all documents in a collection. + + > #### On using this function {: .info} + > As of this writing (v0.5.0), there's no built-in way of deleting + > all documents via [Typesense docs](https://github.com/typesense/typesense/issues/1613#issuecomment-1994986258). + > This function uses `delete_by_query` under the hood. + """ + @doc since: "0.5.0" + @spec delete_all_documents(Connection.t(), module() | String.t()) :: response() + def delete_all_documents(conn \\ Connection.new(), collection_name) + + def delete_all_documents(conn, collection_name) when is_binary(collection_name) do + delete_documents_by_query(conn, collection_name, %{filter_by: "#{collection_name}_id:>=0"}) + end + + def delete_all_documents(conn, collection_name) when is_atom(collection_name) do + name = collection_name.__schema__(:source) + + virtual_field = + :virtual_fields + |> collection_name.__schema__() + |> Enum.filter(&String.contains?(to_string(&1), "_id")) + |> hd() + |> to_string + + delete_documents_by_query(conn, name, %{filter_by: "#{virtual_field}:>0"}) + end + + # @spec do_delete_all_documents(Connection.t(), String.t()) :: response() + # defp do_delete_all_documents(conn, collection_name) do + # path = Path.join([ @collections_path, collection_name, @documents_path ]) + # HttpClient.request(conn, %{method: :delete, path: path}) + # end end diff --git a/lib/ex_typesense/http_client.ex b/lib/ex_typesense/http_client.ex index 138e129..7473e1c 100644 --- a/lib/ex_typesense/http_client.ex +++ b/lib/ex_typesense/http_client.ex @@ -15,8 +15,6 @@ defmodule ExTypesense.HttpClient do @typedoc since: "0.1.0" @type request_path() :: String.t() - @api_header_name ~c"X-TYPESENSE-API-KEY" - @doc since: "0.1.0" @spec get_host :: String.t() | nil def get_host, do: Application.get_env(:ex_typesense, :host) @@ -127,113 +125,4 @@ defmodule ExTypesense.HttpClient do {:error, Jason.decode!(response.body)["message"]} end end - - @doc """ - Req client. - - ## Examples - iex> HttpClient.run(:get, "/collections") - {:ok, - [%{ - "created_at" => 123456789, - "default_sorting_field" => "num_employees", - "fields" => [...], - "name" => "companies", - "num_documents" => 0, - "symbols_to_index" => [], - "token_separators" => [] - }] - } - """ - @doc since: "0.1.0" - @deprecated "Use request/2 instead" - @spec run(request_method(), request_path(), request_body(), map()) :: - {:ok, map()} | {:error, map()} - def run(request_method, request_path, body \\ nil, query \\ %{}) do - url = %URI{ - scheme: get_scheme() || "https", - host: get_host(), - port: get_port() || 443, - path: request_path, - query: URI.encode_query(query) - } - - response = - %Req.Request{ - body: body, - method: request_method, - url: url - } - |> Req.Request.put_header("x-typesense-api-key", api_key()) - |> Req.Request.append_error_steps(retry: &Req.Steps.retry/1) - |> Req.Steps.encode_body() - |> Req.Request.run!() - - case response.status in 200..299 do - true -> - {:ok, Jason.decode!(response.body)} - - false -> - {:error, Jason.decode!(response.body)["message"]} - end - end - - @doc since: "0.3.0" - @deprecated "Use request/2 instead" - @spec httpc_run(URI.t(), atom(), String.t(), list()) :: {:ok, map()} | {:error, map()} - def httpc_run(uri, method, payload, content_type \\ ~c"application/json") do - uri = %URI{ - scheme: get_scheme(), - host: get_host(), - port: get_port(), - path: uri.path, - query: uri.query - } - - api_key = String.to_charlist(api_key()) - - headers = [{@api_header_name, api_key}] - - request = { - URI.to_string(uri), - headers, - content_type, - payload - } - - :ok = :ssl.start() - - http_opts = [ - ssl: [ - {:versions, [:"tlsv1.2"]}, - verify: :verify_peer, - cacerts: :public_key.cacerts_get(), - customize_hostname_check: [ - match_fun: :public_key.pkix_verify_hostname_match_fun(:https) - ] - ], - timeout: 3_600, - connect_timeout: 3_600 - ] - - case :httpc.request(method, request, http_opts, []) do - {:ok, {_status_code, _headers, message}} -> - case Jason.decode(message) do - {:ok, message} -> - {:ok, message} - - {:error, %Jason.DecodeError{data: data}} -> - message = - data - |> String.split("\n", trim: true) - |> Stream.map(&Jason.decode!/1) - |> Enum.to_list() - - {:ok, message} - end - - {:error, reason} -> - {:error, reason} - end - end end diff --git a/lib/ex_typesense/result_parser.ex b/lib/ex_typesense/result_parser.ex index 1cd3417..64df763 100644 --- a/lib/ex_typesense/result_parser.ex +++ b/lib/ex_typesense/result_parser.ex @@ -5,25 +5,19 @@ defmodule ExTypesense.ResultParser do @spec hits_to_query(Enum.t(), module()) :: Ecto.Query.t() def hits_to_query(hits, module_name) do - case Enum.empty?(hits) do - true -> - module_name - |> where([i], i.id in []) + if Enum.empty?(hits) do + module_name + |> where([i], i.id in []) + else + primary_field = module_name.__schema__(:source) <> "_id" - false -> - # this assumes the fk pointing to primary, e.g. post_id - first_virtual_field = hd(module_name.__schema__(:virtual_fields)) + values = + Enum.map(hits, fn %{"document" => document} -> + document[primary_field] + end) - # this assumes the pk, e.g. posts' "id" that matches fk above - primary_key = hd(module_name.__schema__(:primary_key)) - - values = - Enum.map(hits, fn %{"document" => document} -> - document[to_string(first_virtual_field)] - end) - - module_name - |> where([i], field(i, ^primary_key) in ^values) + module_name + |> where([i], i.id in ^values) end end end diff --git a/lib/ex_typesense/search.ex b/lib/ex_typesense/search.ex index 1ae7412..cac3c81 100644 --- a/lib/ex_typesense/search.ex +++ b/lib/ex_typesense/search.ex @@ -41,23 +41,22 @@ defmodule ExTypesense.Search do """ @doc since: "0.1.0" @spec search(Connection.t(), module() | String.t(), map()) :: response() - def search(conn \\ Connection.new(), module_or_collection_name, params) + def search(conn \\ Connection.new(), collection_name, params) - def search(conn, module_name, params) when is_atom(module_name) and is_map(params) do - collection_name = module_name.__schema__(:source) + def search(conn, collection_name, params) when is_atom(collection_name) and is_map(params) do + collection = collection_name.__schema__(:source) path = Path.join([ @collections_path, - collection_name, + collection, @documents_path, @search_path ]) {:ok, result} = HttpClient.request(conn, %{method: :get, path: path, query: params}) - # {:ok, result} = HttpClient.run(:get, path, nil, params) - ResultParser.hits_to_query(result["hits"], module_name) + ResultParser.hits_to_query(result["hits"], collection_name) end def search(conn, collection_name, params) when is_binary(collection_name) and is_map(params) do @@ -70,6 +69,5 @@ defmodule ExTypesense.Search do ]) HttpClient.request(conn, %{method: :get, path: path, query: params}) - # HttpClient.run(:get, path, nil, params) end end diff --git a/lib/ex_typesense/test_schema/catalog.ex b/lib/ex_typesense/test_schema/catalog.ex index 03f28a9..5e12960 100644 --- a/lib/ex_typesense/test_schema/catalog.ex +++ b/lib/ex_typesense/test_schema/catalog.ex @@ -7,9 +7,9 @@ defmodule ExTypesense.TestSchema.Catalog do defimpl Jason.Encoder, for: __MODULE__ do def encode(value, opts) do value - |> Map.take([:catalog_id, :name, :description]) + |> Map.take([:catalogs_id, :name, :description]) |> Enum.map(fn {key, val} -> - if key === :catalog_id, do: {key, Map.get(value, :id)}, else: {key, val} + if key === :catalogs_id, do: {key, Map.get(value, :id)}, else: {key, val} end) |> Enum.into(%{}) |> Jason.Encode.map(opts) @@ -19,15 +19,17 @@ defmodule ExTypesense.TestSchema.Catalog do schema "catalogs" do field(:name, :string) field(:description, :string) - field(:catalog_id, :integer, virtual: true) + field(:catalogs_id, :integer, virtual: true) end @impl ExTypesense def get_field_types do + primary_field = __MODULE__.__schema__(:source) <> "_id" + %{ - default_sorting_field: "catalog_id", + default_sorting_field: primary_field, fields: [ - %{name: "catalog_id", type: "int32"}, + %{name: primary_field, type: "int32"}, %{name: "name", type: "string"}, %{name: "description", type: "string"} ] diff --git a/lib/ex_typesense/test_schema/person.ex b/lib/ex_typesense/test_schema/person.ex index db468e2..4a4e725 100644 --- a/lib/ex_typesense/test_schema/person.ex +++ b/lib/ex_typesense/test_schema/person.ex @@ -7,9 +7,9 @@ defmodule ExTypesense.TestSchema.Person do defimpl Jason.Encoder, for: __MODULE__ do def encode(value, opts) do value - |> Map.take([:person_id, :name, :country]) + |> Map.take([:persons_id, :name, :country]) |> Enum.map(fn {key, val} -> - if key === :person_id, do: {key, Map.get(value, :id)}, else: {key, val} + if key === :persons_id, do: {key, Map.get(value, :id)}, else: {key, val} end) |> Enum.into(%{}) |> Jason.Encode.map(opts) @@ -19,15 +19,17 @@ defmodule ExTypesense.TestSchema.Person do schema "persons" do field(:name, :string) field(:country, :string) - field(:person_id, :integer, virtual: true) + field(:persons_id, :integer, virtual: true) end @impl ExTypesense def get_field_types do + primary_field = __MODULE__.__schema__(:source) <> "_id" + %{ - default_sorting_field: "person_id", + default_sorting_field: primary_field, fields: [ - %{name: "person_id", type: "int32"}, + %{name: primary_field, type: "int32"}, %{name: "name", type: "string"}, %{name: "country", type: "string"} ] diff --git a/test/document_test.exs b/test/document_test.exs index 49c409c..6cb2733 100644 --- a/test/document_test.exs +++ b/test/document_test.exs @@ -8,16 +8,16 @@ defmodule DocumentTest do name: "doc_companies", fields: [ %{name: "company_name", type: "string"}, - %{name: "company_id", type: "int32"}, + %{name: "doc_companies_id", type: "int32"}, %{name: "country", type: "string"} ], - default_sorting_field: "company_id" + default_sorting_field: "doc_companies_id" } document = %{ collection_name: "doc_companies", company_name: "Test", - company_id: 1001, + doc_companies_id: 1001, country: "US" } @@ -26,21 +26,17 @@ defmodule DocumentTest do documents: [ %{ company_name: "Industrial Mills, Co.", - company_id: 990, + doc_companies_id: 990, country: "US" }, %{ company_name: "Washing Machine, Inc.", - company_id: 10, + doc_companies_id: 10, country: "US" } ] } - %{schema: schema, document: document, multiple_documents: multiple_documents} - end - - setup %{schema: schema} do ExTypesense.create_collection(schema) ExTypesense.create_collection(Person) @@ -49,7 +45,7 @@ defmodule DocumentTest do ExTypesense.drop_collection(Person) end) - :ok + %{schema: schema, document: document, multiple_documents: multiple_documents} end test "error: get unknown document", %{schema: schema} do @@ -117,13 +113,15 @@ defmodule DocumentTest do test "success: deletes a document using map", %{document: document} do {:ok, %{"id" => id}} = ExTypesense.create_document(document) - assert {:ok, _} = ExTypesense.delete_document(document.collection_name, String.to_integer(id)) + assert {:ok, _} = + ExTypesense.delete_document({document.collection_name, String.to_integer(id)}) end test "success: deletes a document by struct" do - person = %Person{id: 0, name: "John Smith", person_id: 0, country: "Brazil"} + person = %Person{id: 99, name: "John Smith", persons_id: 99, country: "Brazil"} - assert {:ok, %{"country" => "Brazil", "id" => "0", "name" => "John Smith", "person_id" => 0}} = + assert {:ok, + %{"country" => "Brazil", "id" => "1", "name" => "John Smith", "persons_id" => 99}} = ExTypesense.create_document(person) assert {:ok, _} = ExTypesense.delete_document(person) @@ -132,13 +130,13 @@ defmodule DocumentTest do test "success: deletes a document by id", %{document: document} do {:ok, %{"id" => id}} = ExTypesense.create_document(document) - assert {:ok, _} = ExTypesense.delete_document(document.collection_name, String.to_integer(id)) + assert {:ok, _} = + ExTypesense.delete_document({document.collection_name, String.to_integer(id)}) end test "success: index multiple documents", %{multiple_documents: multiple_documents} do - ExTypesense.index_multiple_documents(multiple_documents) - |> Kernel.===({:ok, [%{"success" => true}, %{"success" => true}]}) - |> assert() + assert {:ok, [%{"success" => true}, %{"success" => true}]} === + ExTypesense.index_multiple_documents(multiple_documents) end test "success: update multiple documents", %{ @@ -165,9 +163,8 @@ defmodule DocumentTest do multiple_documents = Map.put(multiple_documents, :documents, [update_1, update_2]) - ExTypesense.update_multiple_documents(multiple_documents) - |> Kernel.===({:ok, [%{"success" => true}, %{"success" => true}]}) - |> assert() + assert {:ok, [%{"success" => true}, %{"success" => true}]} === + ExTypesense.update_multiple_documents(multiple_documents) {:ok, first} = ExTypesense.get_document(schema.name, String.to_integer(first_id)) {:ok, second} = ExTypesense.get_document(schema.name, String.to_integer(second_id)) @@ -177,9 +174,8 @@ defmodule DocumentTest do end test "success: upsert multiple documents", %{multiple_documents: multiple_documents} do - ExTypesense.upsert_multiple_documents(multiple_documents) - |> Kernel.===({:ok, [%{"success" => true}, %{"success" => true}]}) - |> assert() + assert {:ok, [%{"success" => true}, %{"success" => true}]} === + ExTypesense.upsert_multiple_documents(multiple_documents) end test "error: upsert multiple documents with struct type" do @@ -192,7 +188,38 @@ defmodule DocumentTest do ExTypesense.upsert_multiple_documents(persons) end - test "success: delete all documents in a collection", %{schema: schema} do - assert %{ok: %{}} == ExTypesense.delete_all_documents(schema.name) + test "success: delete all documents using Ecto schema module" do + person = %Person{id: 1, name: "John Doe", persons_id: 1, country: "Scotland"} + + assert {:ok, %{"country" => "Scotland", "id" => "0", "name" => "John Doe", "persons_id" => 1}} = + ExTypesense.create_document(person) + + assert {:ok, %{"num_deleted" => 1}} == ExTypesense.delete_all_documents(Person) + end + + test "success: delete all documents in a collection" do + multiple_documents = %{ + collection_name: "doc_companies", + documents: [ + %{ + company_name: "Boca Cola", + doc_companies_id: 227, + country: "SG" + }, + %{ + company_name: "Motor, Inc.", + doc_companies_id: 99, + country: "TH" + } + ] + } + + assert {:ok, [%{"success" => true}, %{"success" => true}]} === + ExTypesense.index_multiple_documents(multiple_documents) + + {:ok, %{"num_deleted" => documents_deleted}} = + ExTypesense.delete_all_documents(multiple_documents.collection_name) + + assert documents_deleted > 0 end end diff --git a/test/search_test.exs b/test/search_test.exs index a5577ef..39c26d2 100644 --- a/test/search_test.exs +++ b/test/search_test.exs @@ -9,16 +9,16 @@ defmodule SearchTest do name: "search_companies", fields: [ %{name: "company_name", type: "string"}, - %{name: "company_id", type: "int32"}, + %{name: "search_companies_id", type: "int32"}, %{name: "country", type: "string"} ], - default_sorting_field: "company_id" + default_sorting_field: "search_companies_id" } document = %{ collection_name: "search_companies", company_name: "Test", - company_id: 1001, + search_companies_id: 1001, country: "US" } @@ -26,7 +26,7 @@ defmodule SearchTest do id: 1002, name: "Rubber Ducky", description: "A tool by articulating a problem in spoken or written natural language.", - catalog_id: 1002 + catalogs_id: 1002 } with %ExTypesense.Collection{} <- ExTypesense.create_collection(schema) do @@ -60,7 +60,7 @@ defmodule SearchTest do query_by: "name" } - assert %Ecto.Query{} = Catalog |> where([p], p.id in ^[catalog.catalog_id]) + assert %Ecto.Query{} = Catalog |> where([p], p.id in ^[catalog.catalogs_id]) assert %Ecto.Query{} = ExTypesense.search(Catalog, params) end From 0d2e892630cb73a0ef9a1dd7972c6fca3cf07a0f Mon Sep 17 00:00:00 2001 From: jaeyson Date: Mon, 8 Jul 2024 14:41:11 +0800 Subject: [PATCH 03/11] fix pattern match on test --- test/document_test.exs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/document_test.exs b/test/document_test.exs index 6cb2733..e626c40 100644 --- a/test/document_test.exs +++ b/test/document_test.exs @@ -121,7 +121,7 @@ defmodule DocumentTest do person = %Person{id: 99, name: "John Smith", persons_id: 99, country: "Brazil"} assert {:ok, - %{"country" => "Brazil", "id" => "1", "name" => "John Smith", "persons_id" => 99}} = + %{"country" => "Brazil", "id" => _, "name" => "John Smith", "persons_id" => 99}} = ExTypesense.create_document(person) assert {:ok, _} = ExTypesense.delete_document(person) @@ -191,7 +191,7 @@ defmodule DocumentTest do test "success: delete all documents using Ecto schema module" do person = %Person{id: 1, name: "John Doe", persons_id: 1, country: "Scotland"} - assert {:ok, %{"country" => "Scotland", "id" => "0", "name" => "John Doe", "persons_id" => 1}} = + assert {:ok, %{"country" => "Scotland", "id" => _, "name" => "John Doe", "persons_id" => 1}} = ExTypesense.create_document(person) assert {:ok, %{"num_deleted" => 1}} == ExTypesense.delete_all_documents(Person) @@ -222,4 +222,8 @@ defmodule DocumentTest do assert documents_deleted > 0 end + + test "success: delete documents by query" do + assert nil === true + end end From e17d364457dbfe0b0262072765ef28da8e58399f Mon Sep 17 00:00:00 2001 From: jaeyson Date: Mon, 8 Jul 2024 14:52:32 +0800 Subject: [PATCH 04/11] update readme for default_sorting_field --- README.md | 22 ++++++++++++++-------- guides/cheatsheet.cheatmd | 30 ++++++++++++++++-------------- lib/ex_typesense.ex | 12 +++++++----- lib/ex_typesense/collection.ex | 24 +++++++----------------- lib/ex_typesense/document.ex | 18 +++++++++--------- 5 files changed, 53 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 231fcf8..1260a55 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,10 @@ There are 2 ways to create a collection, either via [Ecto schema](https://hexdoc #### Option 1: using Ecto -In this example, we're adding `person_id` that points to the id of `persons` schema. +In this example, we're adding `persons_id` that points to the id of `persons` schema. + +> **Note**: we're using `_id`. If you have table +> e.g. named `persons`, it'll be `persons_id`. ```elixir defmodule Person do @@ -159,11 +162,11 @@ defmodule Person do defimpl Jason.Encoder, for: __MODULE__ do def encode(value, opts) do value - |> Map.take([:id, :person_id, :name, :country]) + |> Map.take([:id, :persons_id, :name, :country]) |> Enum.map(fn {key, val} -> cond do key === :id -> {key, to_string(Map.get(value, :id))} - key === :person_id -> {key, Map.get(value, :id)} + key === :persons_id -> {key, Map.get(value, :id)} true -> {key, val} end end) @@ -175,15 +178,18 @@ defmodule Person do schema "persons" do field(:name, :string) field(:country, :string) - field(:person_id, :integer, virtual: true) + field(:persons_id, :integer, virtual: true) end @impl ExTypesense def get_field_types do + primary_field = __MODULE__.__schema__(:source) <> "_id" + %{ - default_sorting_field: "person_id", + # Or might as well just write persons_id instead. Up to you. + default_sorting_field: primary_field, fields: [ - %{name: "person_id", type: "int32"}, + %{name: primary_field, type: "int32"}, %{name: "name", type: "string"}, %{name: "country", type: "string"} ] @@ -205,10 +211,10 @@ schema = %{ name: "companies", fields: [ %{name: "company_name", type: "string"}, - %{name: "company_id", type: "int32"}, + %{name: "companies_id", type: "int32"}, %{name: "country", type: "string"} ], - default_sorting_field: "company_id" + default_sorting_field: "companies_id" } ExTypesense.create_collection(schema) diff --git a/guides/cheatsheet.cheatmd b/guides/cheatsheet.cheatmd index 4c8b867..1be467f 100644 --- a/guides/cheatsheet.cheatmd +++ b/guides/cheatsheet.cheatmd @@ -15,9 +15,9 @@ defmodule MyApp.Listings.Company do defimpl Jason.Encoder, for: __MODULE__ do def encode(value, opts) do value - |> Map.take([:company_id, :name, :country]) + |> Map.take([:companies_id, :name, :country]) |> Enum.map(fn {key, val} -> - if key === :company_id, do: {key, Map.get(value, :id)}, else: {key, val} + if key === :companies_id, do: {key, Map.get(value, :id)}, else: {key, val} end) |> Enum.into(%{}) |> Jason.Encode.map(opts) @@ -27,15 +27,17 @@ defmodule MyApp.Listings.Company do schema "companies" do field(:name, :string) field(:country, :string) - field(:company_id, :integer, virtual: true) + field(:companies_id, :integer, virtual: true) end @impl ExTypesense def get_field_types do + primary_field = __MODULE__.__schema__(:source) <> "_id" + %{ - default_sorting_field: "company_id", + default_sorting_field: primary_field, fields: [ - %{name: "company_id", type: "int32"}, + %{name: primary_field, type: "int32"}, %{name: "name", type: "string"}, %{name: "country", type: "string"} ] @@ -48,7 +50,7 @@ iex> ExTypesense.create_collection(Company) {:ok, %ExTypesense.Collection{ "created_at" => 1234567890, - "default_sorting_field" => "company_id", + "default_sorting_field" => "companies_id", "fields" => [...], "name" => "companies", "num_documents" => 0, @@ -67,17 +69,17 @@ iex> schema = ...> name: "companies", ...> fields: [ ...> %{name: "company_name", type: "string"}, -...> %{name: "num_employees", type: "int32"}, +...> %{name: "companies_id", type: "int32"}, ...> %{name: "country", type: "string", facet: true} ...> ], -...> default_sorting_field: "num_employees" +...> default_sorting_field: "companies_id" ...> } iex> ExTypesense.create_collection(schema) {:ok, %ExTypesense.Collection{ "created_at" => 1234567890, - "default_sorting_field" => "num_employees", + "default_sorting_field" => "companies_id", "fields" => [...], "name" => "companies", "num_documents" => 0, @@ -110,7 +112,7 @@ iex> ExTypesense.update_collection("companies", schema) %ExTypesense.Collection{ "created_at" => nil, "name" => nil, - "default_sorting_field" => nil, + "default_sorting_field" => "companies_id", "fields" => [...], "num_documents" => 0, "symbols_to_index" => [], @@ -133,7 +135,7 @@ iex> ExTypesense.create_document(post, :create) {:ok, %{ "id" => "12", - "post_id" => 12, + "posts_id" => 12, "title" => "the quick brown fox", "collection_name" => "posts" } @@ -152,7 +154,7 @@ iex> ExTypesense.update_document(post, 0) %{ "id" => "0", "collection_name" => "posts", - "post_id" => 34, + "posts_id" => 34, "title" => "test", "description" => "lorem ipsum" } @@ -170,7 +172,7 @@ iex> ExTypesense.delete_document(Post, 0) %{ "id" => "0", "collection_name" => "posts", - "post_id" => 34, + "posts_id" => 34, "title" => "test", "description" => "lorem ipsum" } @@ -199,7 +201,7 @@ iex> ExTypesense.search(Post, params) %{ "id" => "0", "collection_name" => "posts", - "post_id" => 34, + "posts_id" => 34, "title" => "test", "description" => "lorem ipsum" } diff --git a/lib/ex_typesense.ex b/lib/ex_typesense.ex index c6976eb..2afe415 100644 --- a/lib/ex_typesense.ex +++ b/lib/ex_typesense.ex @@ -14,11 +14,11 @@ defmodule ExTypesense do defimpl Jason.Encoder, for: __MODULE__ do def encode(value, opts) do value - |> Map.take([:id, :person_id, :name, :age]) + |> Map.take([:id, :persons_id, :name, :age]) |> Enum.map(fn {key, val} -> cond do key === :id -> {key, to_string(Map.get(value, :id))} - key === :person_id -> {key, Map.get(value, :id)} + key === :persons_id -> {key, Map.get(value, :id)} true -> {key, val} end end) @@ -30,16 +30,18 @@ defmodule ExTypesense do schema "persons" do field :name, :string field :age, :integer - field :person_id, :integer, virtual: true + field :persons_id, :integer, virtual: true end @impl ExTypesense def get_field_types do + primary_field = __MODULE__.__schema__(:source) <> "_id" + %{ - default_sorting_field: "person_id", + default_sorting_field: primary_field, fields: [ - %{name: "person_id", type: "int32"}, + %{name: primary_field, type: "int32"}, %{name: "name", type: "string"}, %{name: "age", type: "integer"} ] diff --git a/lib/ex_typesense/collection.ex b/lib/ex_typesense/collection.ex index 204063e..0bb2049 100644 --- a/lib/ex_typesense/collection.ex +++ b/lib/ex_typesense/collection.ex @@ -174,15 +174,15 @@ defmodule ExTypesense.Collection do ...> name: "companies", ...> fields: [ ...> %{name: "company_name", type: "string"}, - ...> %{name: "company_id", type: "int32"}, + ...> %{name: "companies_id", type: "int32"}, ...> %{name: "country", type: "string", facet: true} ...> ], - ...> default_sorting_field: "company_id" + ...> default_sorting_field: "companies_id" ...> } iex> ExTypesense.create_collection(schema) %ExTypesense.Collection{ created_at: 1234567890, - default_sorting_field: "company_id", + default_sorting_field: "companies_id", fields: [...], name: "companies", num_documents: 0, @@ -193,7 +193,7 @@ defmodule ExTypesense.Collection do iex> ExTypesense.create_collection(Person) %ExTypesense.Collection{ created_at: 1234567890, - default_sorting_field: "person_id", + default_sorting_field: "persons_id", fields: [...], name: "persons", num_documents: 0, @@ -254,18 +254,7 @@ defmodule ExTypesense.Collection do %ExTypesense.Collection{ created_at: 1234567890, name: companies, - default_sorting_field: "company_id", - fields: [...], - num_documents: 0, - symbols_to_index: [], - token_separators: [] - } - - iex> ExTypesense.update_collection_fields(Company, fields) - %ExTypesense.Collection{ - created_at: 1234567890, - name: companies, - default_sorting_field: "company_id", + default_sorting_field: "companies_id", fields: [...], num_documents: 0, symbols_to_index: [], @@ -304,7 +293,8 @@ defmodule ExTypesense.Collection do @doc """ Permanently drops a collection by collection name or module name. - **Note**: dropping a collection does not remove the referenced alias, only the indexed documents. + **Note**: dropping a collection does not remove the referenced + alias, only the indexed documents. """ @doc since: "0.1.0" @spec drop_collection(Connection.t(), name :: String.t() | module()) :: response() diff --git a/lib/ex_typesense/document.ex b/lib/ex_typesense/document.ex index 7d89b33..a4d520e 100644 --- a/lib/ex_typesense/document.ex +++ b/lib/ex_typesense/document.ex @@ -235,7 +235,7 @@ defmodule ExTypesense.Document do ...> %{ ...> id: "34", ...> collection_name: "posts", - ...> post_id: 34, + ...> posts_id: 34, ...> title: "the quick brown fox", ...> description: "jumps over the lazy dog" ...> } @@ -244,7 +244,7 @@ defmodule ExTypesense.Document do %{ "id" => "34", "collection_name" => "posts", - "post_id" => 34, + "posts_id" => 34, "title" => "the quick brown fox", "description" => "jumps over the lazy dog" } @@ -284,7 +284,7 @@ defmodule ExTypesense.Document do ...> %{ ...> id: "94", ...> collection_name: "posts", - ...> post_id: 94, + ...> posts_id: 94, ...> title: "the quick brown fox" ...> } iex> ExTypesense.create_document(post) @@ -292,7 +292,7 @@ defmodule ExTypesense.Document do ...> %{ ...> id: "94", ...> collection_name: "posts", - ...> post_id: 94, + ...> posts_id: 94, ...> title: "test" ...> } iex> ExTypesense.update_document(updated_post) @@ -300,8 +300,8 @@ defmodule ExTypesense.Document do %{ "id" => "94", "collection_name" => "posts", - "post_id" => 94, - "title" => "test" + "posts_id" => 94, + "title" => "sample post" } } """ @@ -386,7 +386,7 @@ defmodule ExTypesense.Document do {:ok, %{ "id" => "1", - "post_id" => 1, + "posts_id" => 1, "title" => "our first post", "collection_name" => "posts" } @@ -403,7 +403,7 @@ defmodule ExTypesense.Document do ...> %{ ...> id: "12", ...> collection_name: "posts", - ...> post_id: 22, + ...> posts_id: 22, ...> title: "the quick brown fox" ...> } iex> ExTypesense.create_document(post) @@ -411,7 +411,7 @@ defmodule ExTypesense.Document do {:ok, %{ "id" => "12", - "post_id" => 22, + "posts_id" => 22, "title" => "the quick brown fox", "collection_name" => "posts" } From 95a77e5b39f8ad797b868ec1d90505cf7f6a44a4 Mon Sep 17 00:00:00 2001 From: jaeyson Date: Tue, 9 Jul 2024 15:38:06 +0800 Subject: [PATCH 05/11] add typedoc tag version --- lib/ex_typesense/collection.ex | 1 + lib/ex_typesense/http_client.ex | 1 + lib/ex_typesense/result_parser.ex | 1 + lib/ex_typesense/search.ex | 2 ++ test/document_test.exs | 3 +-- 5 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/ex_typesense/collection.ex b/lib/ex_typesense/collection.ex index 0bb2049..dd5fe20 100644 --- a/lib/ex_typesense/collection.ex +++ b/lib/ex_typesense/collection.ex @@ -96,6 +96,7 @@ defmodule ExTypesense.Collection do symbols_to_index: [] ] + @typedoc since: "0.1.0" @type t() :: %__MODULE__{ created_at: integer(), name: String.t(), diff --git a/lib/ex_typesense/http_client.ex b/lib/ex_typesense/http_client.ex index 7473e1c..684f598 100644 --- a/lib/ex_typesense/http_client.ex +++ b/lib/ex_typesense/http_client.ex @@ -38,6 +38,7 @@ defmodule ExTypesense.HttpClient do > function will still return the key and accessible inside > shell (assuming bad actors [pun unintended `:/`] can get in as well). """ + @doc since: "0.1.0" @spec api_key :: String.t() | nil def api_key, do: Application.get_env(:ex_typesense, :api_key) diff --git a/lib/ex_typesense/result_parser.ex b/lib/ex_typesense/result_parser.ex index 64df763..55e7357 100644 --- a/lib/ex_typesense/result_parser.ex +++ b/lib/ex_typesense/result_parser.ex @@ -3,6 +3,7 @@ defmodule ExTypesense.ResultParser do @moduledoc false + @doc since: "0.1.0" @spec hits_to_query(Enum.t(), module()) :: Ecto.Query.t() def hits_to_query(hits, module_name) do if Enum.empty?(hits) do diff --git a/lib/ex_typesense/search.ex b/lib/ex_typesense/search.ex index cac3c81..08e5f0d 100644 --- a/lib/ex_typesense/search.ex +++ b/lib/ex_typesense/search.ex @@ -13,6 +13,8 @@ defmodule ExTypesense.Search do @collections_path @root_path <> "collections" @documents_path "documents" @search_path "search" + + @typedoc since: "0.1.0" @type response :: Ecto.Query.t() | {:ok, map()} | {:error, map()} @doc """ diff --git a/test/document_test.exs b/test/document_test.exs index e626c40..7a345ad 100644 --- a/test/document_test.exs +++ b/test/document_test.exs @@ -120,8 +120,7 @@ defmodule DocumentTest do test "success: deletes a document by struct" do person = %Person{id: 99, name: "John Smith", persons_id: 99, country: "Brazil"} - assert {:ok, - %{"country" => "Brazil", "id" => _, "name" => "John Smith", "persons_id" => 99}} = + assert {:ok, %{"country" => "Brazil", "id" => _, "name" => "John Smith", "persons_id" => 99}} = ExTypesense.create_document(person) assert {:ok, _} = ExTypesense.delete_document(person) From 8c46dd602cfae294d9551a59166edf87343fe616 Mon Sep 17 00:00:00 2001 From: jaeyson Date: Tue, 9 Jul 2024 16:18:46 +0800 Subject: [PATCH 06/11] add test for deleting documents by query --- lib/ex_typesense/document.ex | 1 + test/document_test.exs | 52 ++++++++++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/lib/ex_typesense/document.ex b/lib/ex_typesense/document.ex index a4d520e..bb5147e 100644 --- a/lib/ex_typesense/document.ex +++ b/lib/ex_typesense/document.ex @@ -500,6 +500,7 @@ defmodule ExTypesense.Document do def delete_documents_by_query(conn, collection_name, %{filter_by: filter_by} = query) when not is_nil(filter_by) and is_binary(filter_by) and is_atom(collection_name) do + collection_name = collection_name.__schema__(:source) path = Path.join([@collections_path, collection_name, @documents_path]) HttpClient.request(conn, %{method: :delete, path: path, query: query}) end diff --git a/test/document_test.exs b/test/document_test.exs index 7a345ad..87c9978 100644 --- a/test/document_test.exs +++ b/test/document_test.exs @@ -48,6 +48,18 @@ defmodule DocumentTest do %{schema: schema, document: document, multiple_documents: multiple_documents} end + setup %{multiple_documents: multiple_documents} do + assert {:ok, %{"num_deleted" => _}} = + ExTypesense.delete_documents_by_query(multiple_documents.collection_name, %{ + filter_by: "doc_companies_id:>=0" + }) + + assert {:ok, %{"num_deleted" => _}} = + ExTypesense.delete_documents_by_query(Person, %{filter_by: "persons_id:>=0"}) + + :ok + end + test "error: get unknown document", %{schema: schema} do unknown_id = 999 message = ~s(Could not find a document with id: #{unknown_id}) @@ -222,7 +234,43 @@ defmodule DocumentTest do assert documents_deleted > 0 end - test "success: delete documents by query" do - assert nil === true + test "success: delete documents by query (Ecto schema)" do + john_toe = %Person{id: 32, name: "John Toe", persons_id: 32, country: "Egypt"} + john_foe = %Person{id: 14, name: "John Foe", persons_id: 14, country: "Cuba"} + + assert {:ok, %{"country" => "Egypt", "id" => _, "name" => "John Toe", "persons_id" => 32}} = + ExTypesense.create_document(john_toe) + + assert {:ok, %{"country" => "Cuba", "id" => _, "name" => "John Foe", "persons_id" => 14}} = + ExTypesense.create_document(john_foe) + + assert {:ok, %{"num_deleted" => 2}} = + ExTypesense.delete_documents_by_query(Person, %{filter_by: "persons_id:>=0"}) + end + + test "success: delete documents by query (map)" do + documents = %{ + collection_name: "doc_companies", + documents: [ + %{ + company_name: "Doctor & Gamble", + doc_companies_id: 19, + country: "ES" + }, + %{ + company_name: "The Daily Bribe", + doc_companies_id: 84, + country: "PH" + } + ] + } + + assert {:ok, [%{"success" => true}, %{"success" => true}]} === + ExTypesense.index_multiple_documents(documents) + + assert {:ok, %{"num_deleted" => 2}} = + ExTypesense.delete_documents_by_query(documents.collection_name, %{ + filter_by: "doc_companies_id:>=0" + }) end end From a5c32e442b22787aa5273fb2f36283f360807d64 Mon Sep 17 00:00:00 2001 From: jaeyson Date: Wed, 10 Jul 2024 18:10:15 +0800 Subject: [PATCH 07/11] add test case for alias not affected when dropping collection --- lib/ex_typesense/test_schema/product.ex | 38 +++++++++++ test/collection_test.exs | 88 +++++++++++++++---------- test/document_test.exs | 29 ++++++++ 3 files changed, 119 insertions(+), 36 deletions(-) create mode 100644 lib/ex_typesense/test_schema/product.ex diff --git a/lib/ex_typesense/test_schema/product.ex b/lib/ex_typesense/test_schema/product.ex new file mode 100644 index 0000000..3b8c0ff --- /dev/null +++ b/lib/ex_typesense/test_schema/product.ex @@ -0,0 +1,38 @@ +defmodule ExTypesense.TestSchema.Product do + use Ecto.Schema + @behaviour ExTypesense + + @moduledoc false + + defimpl Jason.Encoder, for: __MODULE__ do + def encode(value, opts) do + value + |> Map.take([:poducts_id, :name, :description]) + |> Enum.map(fn {key, val} -> + if key === :products_id, do: {key, Map.get(value, :id)}, else: {key, val} + end) + |> Enum.into(%{}) + |> Jason.Encode.map(opts) + end + end + + schema "products" do + field(:name, :string) + field(:description, :string) + field(:products_id, :integer, virtual: true) + end + + @impl ExTypesense + def get_field_types do + primary_field = __MODULE__.__schema__(:source) <> "_id" + + %{ + default_sorting_field: primary_field, + fields: [ + %{name: primary_field, type: "int32"}, + %{name: "name", type: "string"}, + %{name: "description", type: "string"} + ] + } + end +end diff --git a/test/collection_test.exs b/test/collection_test.exs index 836e3bd..072ef54 100644 --- a/test/collection_test.exs +++ b/test/collection_test.exs @@ -1,6 +1,8 @@ defmodule CollectionTest do use ExUnit.Case, async: true + alias ExTypesense.TestSchema.Product + setup_all do schema = %{ name: "collection_companies", @@ -14,22 +16,47 @@ defmodule CollectionTest do on_exit(fn -> ExTypesense.drop_collection(schema.name) + ExTypesense.drop_collection(Product) end) %{schema: schema} end test "success: create and drop collection", %{schema: schema} do - collection = ExTypesense.create_collection(schema) - assert %ExTypesense.Collection{} = collection + products = ExTypesense.create_collection(Product) + collection_companies = ExTypesense.create_collection(schema) + + assert %ExTypesense.Collection{} = products + assert %ExTypesense.Collection{} = collection_companies + + ExTypesense.drop_collection(Product) + assert {:error, "Not Found"} === ExTypesense.get_collection(Product) ExTypesense.drop_collection(schema.name) assert {:error, "Not Found"} === ExTypesense.get_collection(schema.name) end - test "error: dropping unknown collection", %{schema: schema} do - message = ~s(No collection with name `#{schema.name}` found.) - assert {:error, message} === ExTypesense.drop_collection(schema.name) + test "success: dropping collection won't affect alias", %{schema: schema} do + assert %ExTypesense.Collection{} = ExTypesense.create_collection(Product) + assert %ExTypesense.Collection{} = ExTypesense.create_collection(schema) + + assert %{"collection_name" => _collection_name, "name" => alias} = + ExTypesense.upsert_collection_alias(schema.name <> "_alias", schema.name) + + ExTypesense.drop_collection(schema.name) + ExTypesense.drop_collection(Product) + + assert {:error, "Not Found"} === ExTypesense.get_collection(schema.name) + assert {:error, "Not Found"} === ExTypesense.get_collection(Product) + + assert %{"collection_name" => _collection_name, "name" => _alias} = + ExTypesense.get_collection_alias(alias) + end + + test "error: dropping unknown collection" do + collection_name = "unknown" + message = ~s(No collection with name `#{collection_name}` found.) + assert {:error, message} === ExTypesense.drop_collection(collection_name) end test "success: get specific collection" do @@ -48,8 +75,8 @@ defmodule CollectionTest do ExTypesense.drop_collection(schema.name) end - test "error: get unknown collection", %{schema: schema} do - assert {:error, "Not Found"} === ExTypesense.get_collection(schema.name) + test "error: get unknown collection" do + assert {:error, "Not Found"} === ExTypesense.get_collection("unknown_collection_name") end test "success: update schema fields", %{schema: schema} do @@ -84,46 +111,35 @@ defmodule CollectionTest do end test "success: list aliases", %{schema: schema} do - ExTypesense.upsert_collection_alias(schema.name, schema.name) - count = length(ExTypesense.list_collection_aliases()) - assert count === 1 + assert %{"collection_name" => _collection_name, "name" => alias} = + ExTypesense.upsert_collection_alias(schema.name <> "_alias", schema.name) - ExTypesense.delete_collection_alias(schema.name) - end + refute Enum.empty?(ExTypesense.list_collection_aliases()) - test "success: create and delete alias", %{schema: schema} do - collection_alias = ExTypesense.upsert_collection_alias(schema.name, schema.name) - assert is_map(collection_alias) === true - assert Enum.empty?(collection_alias) === false + assert %{"collection_name" => _collection_name, "name" => _alias} = + ExTypesense.delete_collection_alias(alias) - ExTypesense.delete_collection_alias(schema.name) - assert {:error, "Not Found"} === ExTypesense.get_collection_alias(schema.name) + assert Enum.empty?(ExTypesense.list_collection_aliases()) end - test "success: get collection name by alias", %{schema: schema} do - %{"collection_name" => collection_name, "name" => collection_alias} = - ExTypesense.upsert_collection_alias(schema.name, schema.name) - - assert collection_name === ExTypesense.get_collection_name(collection_alias) - - ExTypesense.delete_collection_alias(schema.name) - end + test "success: create, get, delete alias", %{schema: schema} do + assert %{"collection_name" => _collection_name, "name" => alias} = + ExTypesense.upsert_collection_alias(schema.name <> "_alias", schema.name) - test "success: get specific alias", %{schema: schema} do - ExTypesense.upsert_collection_alias(schema.name, schema.name) - collection_alias = ExTypesense.get_collection_alias(schema.name) + assert %{"collection_name" => _collection_name, "name" => alias} = + ExTypesense.get_collection_alias(alias) - assert is_map(collection_alias) - assert collection_alias["name"] === schema.name + assert %{"collection_name" => _collection_name, "name" => alias} = + ExTypesense.delete_collection_alias(alias) - ExTypesense.delete_collection_alias(schema.name) + assert {:error, "Not Found"} === ExTypesense.get_collection_alias(alias) end - test "error: get unknown alias", %{schema: schema} do - assert {:error, "Not Found"} === ExTypesense.get_collection_alias(schema.name) + test "error: get unknown alias" do + assert {:error, "Not Found"} === ExTypesense.get_collection_alias("unknown_alias") end - test "error: delete unknown alias", %{schema: schema} do - assert {:error, "Not Found"} === ExTypesense.delete_collection_alias(schema.name) + test "error: delete unknown alias" do + assert {:error, "Not Found"} === ExTypesense.delete_collection_alias("unknown_alias") end end diff --git a/test/document_test.exs b/test/document_test.exs index 87c9978..4d15c6f 100644 --- a/test/document_test.exs +++ b/test/document_test.exs @@ -208,6 +208,35 @@ defmodule DocumentTest do assert {:ok, %{"num_deleted" => 1}} == ExTypesense.delete_all_documents(Person) end + test "success: deleting all documents won't drop the collection" do + multiple_documents = %{ + collection_name: "doc_companies", + documents: [ + %{ + company_name: "Boca Cola", + doc_companies_id: 227, + country: "SG" + }, + %{ + company_name: "Motor, Inc.", + doc_companies_id: 99, + country: "TH" + } + ] + } + + assert {:ok, [%{"success" => true}, %{"success" => true}]} === + ExTypesense.index_multiple_documents(multiple_documents) + + {:ok, %{"num_deleted" => documents_deleted}} = + ExTypesense.delete_all_documents(multiple_documents.collection_name) + + assert documents_deleted > 0 + + assert %ExTypesense.Collection{name: "doc_companies"} = + ExTypesense.get_collection(multiple_documents.collection_name) + end + test "success: delete all documents in a collection" do multiple_documents = %{ collection_name: "doc_companies", From 454d4680931bbf804f32ab0921de318465be11e2 Mon Sep 17 00:00:00 2001 From: jaeyson Date: Wed, 10 Jul 2024 18:15:02 +0800 Subject: [PATCH 08/11] add assertion to list aliases --- test/collection_test.exs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/collection_test.exs b/test/collection_test.exs index 072ef54..c0ac8d5 100644 --- a/test/collection_test.exs +++ b/test/collection_test.exs @@ -51,6 +51,9 @@ defmodule CollectionTest do assert %{"collection_name" => _collection_name, "name" => _alias} = ExTypesense.get_collection_alias(alias) + + assert [%{"collection_name" => _collection_name, "name" => _alias}] = + ExTypesense.list_collection_aliases() end test "error: dropping unknown collection" do From 58c3f4bc80e17f96a9fff521a123b6150222e669 Mon Sep 17 00:00:00 2001 From: jaeyson Date: Wed, 10 Jul 2024 18:43:26 +0800 Subject: [PATCH 09/11] add test case for dropping collection deletes all documents --- test/collection_test.exs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/collection_test.exs b/test/collection_test.exs index c0ac8d5..91a1f7a 100644 --- a/test/collection_test.exs +++ b/test/collection_test.exs @@ -56,6 +56,22 @@ defmodule CollectionTest do ExTypesense.list_collection_aliases() end + test "success: dropping collection deletes all documents", %{schema: schema} do + ExTypesense.create_collection(schema) + + multiple_documents = %{ + collection_name: "collection_companies", + documents: [ + %{company_name: "Noogle, Inc.", company_id: 56, country: "AO"}, + %{company_name: "Tikipedia", company_id: 62, country: "BD"} + ] + } + + assert {:ok, _} = ExTypesense.upsert_multiple_documents(multiple_documents) + assert %ExTypesense.Collection{} = ExTypesense.drop_collection(schema.name) + assert {:error, "Not Found"} = ExTypesense.get_document(schema.name, 1) + end + test "error: dropping unknown collection" do collection_name = "unknown" message = ~s(No collection with name `#{collection_name}` found.) From 4e7778c8799d213ecfa15f06bc95c778101695dd Mon Sep 17 00:00:00 2001 From: jaeyson Date: Sat, 13 Jul 2024 21:18:04 +0800 Subject: [PATCH 10/11] update readme and changelog for v0.5.0 --- CHANGELOG.md | 21 +++++++++++++++++++++ README.md | 5 ++--- mix.exs | 2 +- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be8df2e..fc697e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ 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.5.0 (2024.07.13) + +### Changed + +* `README` regarding `default_sorting_field`, where it joins the table name with "_id" (e.g. `images` is `images_id` instead of `image_id`). + +### Added + +* Function: [delete by query](https://typesense.org/docs/26.0/api/documents.html#delete-by-query). +* Function: [delete all documents](https://github.com/typesense/typesense/issues/1613#issuecomment-1994986258) in a collection. +* Collection's schema [field parameters](https://typesense.org/docs/26.0/api/collections.html#field-parameters): + - `:vec_dist` + - `:store` + - `:reference` + - `:range_index` + - `:stem` + +### Removed + +- `HttpClient.run` and `HttpClient.httpc_run` function (use `HttpClient.request`). + ## 0.4.3 (2024.07.03) ### Changed diff --git a/README.md b/README.md index 1260a55..1ae8bc8 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Typesense client for Elixir with support for your Ecto schemas. -> **Note**: Breaking changes if you're upgrading from `0.3.x` to upcoming `0.5.x` version. +> **Note**: Breaking changes if you're upgrading from `0.3.x` to `0.5.x` version. ## Todo @@ -28,7 +28,7 @@ Add `:ex_typesense` to your list of dependencies in the Elixir project config fi def deps do [ # From default Hex package manager - {:ex_typesense, "~> 0.4"} + {:ex_typesense, "~> 0.5"} # Or from GitHub repository, if you want to latest greatest from main branch {:ex_typesense, git: "https://github.com/jaeyson/ex_typesense.git"} @@ -118,7 +118,6 @@ conn = Map.from_struct(MyApp.Credential) # NOTE: create a collection and import documents # first before using the command below ExTypesense.search(conn, collection_name, query) - ``` Or you don't want to change the fields in your Ecto schema, thus you convert it to map: diff --git a/mix.exs b/mix.exs index edebc77..d48df7f 100644 --- a/mix.exs +++ b/mix.exs @@ -2,7 +2,7 @@ defmodule ExTypesense.MixProject do use Mix.Project @source_url "https://github.com/jaeyson/ex_typesense" - @version "0.4.3" + @version "0.5.0" def project do [ From ad5137cb6a6a567c54bcb5e9831f9e21145dc7b5 Mon Sep 17 00:00:00 2001 From: jaeyson Date: Sat, 13 Jul 2024 21:24:28 +0800 Subject: [PATCH 11/11] update cheatsheet --- guides/cheatsheet.cheatmd | 11 +++++++++++ lib/ex_typesense/document.ex | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/guides/cheatsheet.cheatmd b/guides/cheatsheet.cheatmd index 1be467f..d9c2721 100644 --- a/guides/cheatsheet.cheatmd +++ b/guides/cheatsheet.cheatmd @@ -181,6 +181,17 @@ iex> ExTypesense.delete_document(Post, 0) {: .wrap} +### Delete a document by query + +```elixir +iex> query = %{ +...> filter_by: "num_employees:>100", +...> batch_size: 100 +...> } +iex> ExTypesense.delete_documents_by_query(Employee, query) +{:ok, %{}} +``` + ### Indexes multiple documents ```elixir diff --git a/lib/ex_typesense/document.ex b/lib/ex_typesense/document.ex index bb5147e..c5e9608 100644 --- a/lib/ex_typesense/document.ex +++ b/lib/ex_typesense/document.ex @@ -483,7 +483,7 @@ defmodule ExTypesense.Document do ...> filter_by: "num_employees:>100", ...> batch_size: 100 ...> } - iex> ExTypesense.delete_documents_by_query(query) + iex> ExTypesense.delete_documents_by_query(Employee, query) {:ok, %{}} """ @doc since: "0.5.0"