Skip to content

Commit

Permalink
Implement Entity / Record reading APIs (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
maennchen committed Jul 18, 2024
1 parent da7a81c commit 42b884f
Show file tree
Hide file tree
Showing 3 changed files with 199 additions and 6 deletions.
87 changes: 87 additions & 0 deletions lib/senzing/g2/engine.ex
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ defmodule Senzing.G2.Engine do
@type record() :: map()
@type record_id() :: String.t()

@type entity() :: map()
@type entity_id() :: pos_integer()

@type redo_record() :: map()

@type data_source() :: String.t()
Expand Down Expand Up @@ -447,6 +450,90 @@ defmodule Senzing.G2.Engine do
do: {:ok, :json.decode(response)}
end

@doc """
This method is used to retrieve a stored record.
See https://docs.senzing.com/python/3/g2engine/getting/index.html#getrecord
## Examples
iex> :ok = Senzing.G2.Engine.add_record(%{"RECORD_ID" => "test id"}, "TEST")
iex> {:ok, _record} = Senzing.G2.Engine.get_record("test id", "TEST")
iex> # record => %{"RECORD_ID" => "test id"}
"""
@doc type: :getting_entities_and_records
@spec get_record(record_id :: record_id(), data_source :: data_source(), opts :: []) :: G2.result(record())
def get_record(record_id, data_source, _opts \\ []) do
with {:ok, response} <- Nif.get_record(data_source, record_id),
do: {:ok, :json.decode(response)}
end

@doc """
This method is used to retrieve information about a specific resolved entity.
See https://docs.senzing.com/python/3/g2engine/getting/index.html#getentitybyrecordid
## Examples
iex> :ok = Senzing.G2.Engine.add_record(%{"RECORD_ID" => "test id"}, "TEST")
iex> {:ok, _record} = Senzing.G2.Engine.Nif.get_entity_by_record_id("TEST", "test id")
iex> # record => %{"RESOLVED_ENTITY" => %{"ENTITY_ID" => 1}}
"""
@doc type: :getting_entities_and_records
@spec get_entity_by_record_id(record_id :: record_id(), data_source :: data_source(), opts :: []) :: G2.result(entity())
def get_entity_by_record_id(record_id, data_source, _opts \\ []) do
with {:ok, response} <- Nif.get_entity_by_record_id(data_source, record_id),
do: {:ok, :json.decode(response)}
end

@doc """
This method is used to retrieve information about a specific resolved entity.
See: https://docs.senzing.com/python/3/g2engine/getting/index.html#getentitybyentityid
## Examples
iex> :ok = Senzing.G2.Engine.add_record(%{"RECORD_ID" => "test id"}, "TEST")
iex> {:ok, %{"RESOLVED_ENTITY" => %{"ENTITY_ID" => entity_id}}} =
...> Senzing.G2.Engine.get_entity_by_record_id("test id", "TEST")
iex> {:ok, _record} = Senzing.G2.Engine.get_entity(entity_id)
iex> # record => %{"RESOLVED_ENTITY" => %{"ENTITY_ID" => 7}}
"""
@doc type: :getting_entities_and_records
@spec get_entity(entity_id :: entity_id(), opts :: []) :: G2.result(entity())
def get_entity(entity_id, _opts \\ []) do
with {:ok, response} <- Nif.get_entity(entity_id),
do: {:ok, :json.decode(response)}
end

@doc """
This method gives information on how an entity composed of a given set of records would look.
See https://docs.senzing.com/python/3/g2engine/getting/index.html#getvirtualentitybyrecordid
## Examples
iex> :ok = Senzing.G2.Engine.add_record(%{"RECORD_ID" => "test id"}, "TEST")
iex> {:ok, _record} = Senzing.G2.Engine.get_virtual_entity([{"test id", "TEST"}])
iex> # record => %{"RESOLVED_ENTITY" => %{"ENTITY_ID" => 1}}
"""
@doc type: :getting_entities_and_records
@spec get_virtual_entity(record_ids :: [{record_id(), data_source()}], opts :: []) :: G2.result(entity())
def get_virtual_entity(record_ids, _opts \\ []) do
record_ids =
record_ids
|> Enum.map(fn {id, data_source} -> %{"DATA_SOURCE" => data_source, "RECORD_ID" => id} end)
|> then(&%{"RECORDS" => &1})
|> :json.encode()
|> IO.iodata_to_binary()

with {:ok, response} <- Nif.get_virtual_entity(record_ids),
do: {:ok, :json.decode(response)}
end

# This method will destroy and perform cleanup for the G2 processing object.
#
# It should be called after all other calls are complete.
Expand Down
55 changes: 54 additions & 1 deletion lib/senzing/g2/engine/nif.ex
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,25 @@ defmodule Senzing.G2.Engine.Nif do
return beam.make(env, .ok, .{});
}
// TODO: Complete
pub fn get_record(env: beam.env, dataSource: []u8, recordId: []u8) !beam.term {
var g2_dataSource = try beam.allocator.dupeZ(u8, dataSource);
var g2_recordId = try beam.allocator.dupeZ(u8, recordId);
var g2_flags: c_longlong = 0; // TODO: Implement
var responseBuf: [*c]u8 = null;
var responseBufSize: usize = 1024;
var initialResponseBuf = try beam.allocator.alloc(u8, responseBufSize);
defer beam.allocator.free(initialResponseBuf);
responseBuf = initialResponseBuf.ptr;
if (G2.G2_getRecord_V2(g2_dataSource, g2_recordId, g2_flags, &responseBuf, &responseBufSize, resize_pointer) != 0) {
var reason = try get_and_clear_last_exception(env);
return beam.make_error_pair(env, reason, .{});
}
return beam.make(env, .{ .ok, responseBuf }, .{});
}
pub fn get_entity_by_record_id(env: beam.env, dataSource: []u8, recordId: []u8) !beam.term {
var g2_dataSource = try beam.allocator.dupeZ(u8, dataSource);
var g2_recordId = try beam.allocator.dupeZ(u8, recordId);
Expand All @@ -393,6 +411,41 @@ defmodule Senzing.G2.Engine.Nif do
return beam.make(env, .{ .ok, responseBuf }, .{});
}
pub fn get_entity(env: beam.env, entityId: c_longlong) !beam.term {
var g2_flags: c_longlong = 0; // TODO: Implement
var responseBuf: [*c]u8 = null;
var responseBufSize: usize = 1024;
var initialResponseBuf = try beam.allocator.alloc(u8, responseBufSize);
defer beam.allocator.free(initialResponseBuf);
responseBuf = initialResponseBuf.ptr;
if (G2.G2_getEntityByEntityID_V2(entityId, g2_flags, &responseBuf, &responseBufSize, resize_pointer) != 0) {
var reason = try get_and_clear_last_exception(env);
return beam.make_error_pair(env, reason, .{});
}
return beam.make(env, .{ .ok, responseBuf }, .{});
}
pub fn get_virtual_entity(env: beam.env, recordIds: []u8) !beam.term {
var g2_recordIds = try beam.allocator.dupeZ(u8, recordIds);
var g2_flags: c_longlong = 1 << 12; // TODO: Implement
var responseBuf: [*c]u8 = null;
var responseBufSize: usize = 1024;
var initialResponseBuf = try beam.allocator.alloc(u8, responseBufSize);
defer beam.allocator.free(initialResponseBuf);
responseBuf = initialResponseBuf.ptr;
if (G2.G2_getVirtualEntityByRecordID_V2(g2_recordIds, g2_flags, &responseBuf, &responseBufSize, resize_pointer) != 0) {
var reason = try get_and_clear_last_exception(env);
return beam.make_error_pair(env, reason, .{});
}
return beam.make(env, .{ .ok, responseBuf }, .{});
}
pub fn destroy(env: beam.env) !beam.term {
if(G2.G2_destroy() != 0) {
var reason = try get_and_clear_last_exception(env);
Expand Down
63 changes: 58 additions & 5 deletions test/senzing/g2/engine_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -162,9 +162,7 @@ defmodule Senzing.G2.EngineTest do

assert :ok = Engine.add_record(%{"RECORD_ID" => id}, "TEST")

# TODO: Use finished fn
assert {:ok, json} = Engine.Nif.get_entity_by_record_id("TEST", id)
assert %{"RESOLVED_ENTITY" => %{"ENTITY_ID" => entity_id}} = :json.decode(json)
assert {:ok, %{"RESOLVED_ENTITY" => %{"ENTITY_ID" => entity_id}}} = Engine.get_entity_by_record_id(id, "TEST")

assert :ok = Engine.reevaluate_entity(entity_id)

Expand Down Expand Up @@ -221,8 +219,63 @@ defmodule Senzing.G2.EngineTest do
"RECORD_ID" => ^id
}} = Engine.delete_record(id, "TEST", with_info: true)

# TODO: Use finished fn
assert {:error, {33, _message}} = Engine.Nif.get_entity_by_record_id("TEST", id)
assert {:error, {33, _message}} = Engine.get_entity_by_record_id(id, "TEST")
end
end

describe inspect(&Engine.get_record/3) do
# TODO: Implement Flags
test "works", %{test: test} do
id = "#{inspect(__MODULE__)}.#{inspect(test)}"

assert :ok = Engine.add_record(%{"RECORD_ID" => id}, "TEST")

assert {:ok, %{"DATA_SOURCE" => "TEST", "RECORD_ID" => ^id}} = Engine.get_record(id, "TEST")
end
end

describe inspect(&Engine.get_entity_by_record_id/3) do
# TODO: Implement Flags
test "works", %{test: test} do
id = "#{inspect(__MODULE__)}.#{inspect(test)}"

assert :ok = Engine.add_record(%{"RECORD_ID" => id}, "TEST")

assert {:ok, %{"RESOLVED_ENTITY" => %{"ENTITY_ID" => _entity_id}}} = Engine.get_entity_by_record_id(id, "TEST")
end
end

describe inspect(&Engine.get_entity/2) do
# TODO: Implement Flags
test "works", %{test: test} do
id = "#{inspect(__MODULE__)}.#{inspect(test)}"

assert :ok = Engine.add_record(%{"RECORD_ID" => id}, "TEST")
assert {:ok, %{"RESOLVED_ENTITY" => %{"ENTITY_ID" => entity_id}}} = Engine.get_entity_by_record_id(id, "TEST")
assert {:ok, %{"RESOLVED_ENTITY" => %{"ENTITY_ID" => ^entity_id}}} = Engine.get_entity(entity_id)
end
end

describe inspect(&Engine.get_virtual_entity/2) do
# TODO: Implement Flags
test "works", %{test: test} do
id_one = "#{inspect(__MODULE__)}.#{inspect(test)}_one"
id_two = "#{inspect(__MODULE__)}.#{inspect(test)}_two"

assert :ok =
Engine.add_record(
%{"RECORD_ID" => id_one, "RECORD_TYPE" => "ORGANIZATION", "PRIMARY_NAME_ORG" => "Apple"},
"TEST"
)

assert :ok =
Engine.add_record(
%{"RECORD_ID" => id_two, "RECORD_TYPE" => "ORGANIZATION", "PRIMARY_NAME_ORG" => "Apple Inc."},
"TEST"
)

assert {:ok, %{"RESOLVED_ENTITY" => %{"ENTITY_ID" => _entity_id, "ENTITY_NAME" => "Apple" <> _}}} =
Engine.get_virtual_entity([{id_one, "TEST"}, {id_two, "TEST"}])
end
end
end

0 comments on commit 42b884f

Please sign in to comment.