{"id":20644038,"url":"https://github.com/adjust/istore","last_synced_at":"2026-03-14T04:12:11.937Z","repository":{"id":47256697,"uuid":"20890217","full_name":"adjust/istore","owner":"adjust","description":"development repo for integer hstore replacement in postgres","archived":false,"fork":false,"pushed_at":"2025-03-14T16:42:29.000Z","size":996,"stargazers_count":35,"open_issues_count":7,"forks_count":3,"subscribers_count":63,"default_branch":"master","last_synced_at":"2025-04-16T02:06:54.856Z","etag":null,"topics":["adjust-kpis-team","adjust-pg-extension"],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/adjust.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2014-06-16T15:34:17.000Z","updated_at":"2025-03-14T16:42:33.000Z","dependencies_parsed_at":"2024-02-16T15:46:52.478Z","dependency_job_id":"3d654b83-85d1-48f0-ba65-a8f6c0a5b360","html_url":"https://github.com/adjust/istore","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adjust%2Fistore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adjust%2Fistore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adjust%2Fistore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adjust%2Fistore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/adjust","download_url":"https://codeload.github.com/adjust/istore/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249183103,"owners_count":21226142,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["adjust-kpis-team","adjust-pg-extension"],"created_at":"2024-11-16T16:14:36.981Z","updated_at":"2026-03-14T04:12:06.868Z","avatar_url":"https://github.com/adjust.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"![CI](https://github.com/adjust/istore/workflows/CI/badge.svg)\n\n# istore\n\nThe idea of istore is to have an integer based hstore (thus the name) which\nsupports operators like + and aggregates like SUM.\n\nThe textual representation of an istore is the same as for hstore\ne.g.\n\n```\n\"-5\"=\u003e\"-25\", \"0\"=\u003e\"10\", \"5\"=\u003e\"0\", \"100\"=\u003e\"30\"\n```\n\nOn istore both keys and values are represented and stored as integer. The extension\ncomes with two types `istore` and `bigistore` the former having `int` and the\nlatter `bigint` as values, keys are `int` for both.\n\nThe use case an optimization for istore is an analytical workload.\nThink of it as showing distributions of whatever can be represented as integer.\n\n### Example\n\nSay you have an event log table where you'd aggregate events with some id  and\nsegmentation by date.\n\n```\nCREATE TABLE event_log AS\nSELECT\n  d::date as date,\n  j as segment,\n  i as id, (random()*1000)::int as count,\n  (random()*100000)::int as revenues\nFROM\n  generate_series(1,50) i,\n  generate_series(1,1000) j,\n  generate_series(current_date - 99, current_date, '1 day') d;\n```\n\nUsing istore you would use the id as key and count / revenues as values.\n\n```\nCREATE TABLE istore_event_log AS\nSELECT\n  date,\n  segment,\n  istore(array_agg(id), array_agg(count)) as counts,\n  istore(array_agg(id), array_agg(revenues)) as revenues\nFROM event_log\nGROUP BY date, segment;\n```\n\nTo summarize the count for a specific `id` you would write\n\n```\nistore_test=# SELECT SUM(counts-\u003e35) from istore_event_log ;\n    sum\n----------\n  50213687\n(1 row)\n\nTime: 29,032 ms\n```\n\ninstead of\n\n```\nistore_test=# SELECT SUM(count) from event_log where id = 35;\n    sum\n----------\n  50213687\n(1 row)\n\nTime: 374,806 ms\n```\n\nWhere you can already see the performance benefit.\n\nWhich is mostly due to the reduced IO.\n\n```\nSELECT pg_size_pretty(pg_table_size('event_log')) as \"without istore\", pg_size_pretty(pg_table_size('istore_event_log')) as \"with istore\";\n  without istore | with istore\n----------------+-------------\n  249 MB         | 87 MB\n```\n\nThe following functions and operators apply to both `istore` and `bigistore` types.\n\n### istore Operators\n\nOperator              | Description                                                           | Example                                   | Result\n---------             | -----------                                                           | -------                                   | ------\nistore -\u003e integer     | get value for key (NULL if not present)                               | '1=\u003e4,2=\u003e5'::istore -\u003e 1                  | 4\nistore -\u003e integer[]   | get values or key (NULL if not present)                               | '1=\u003e4,2=\u003e5'::istore -\u003e Array[1,3]         | {4,NULL}\nistore ? integer      | does istore contain key?                                              | '1=\u003e4,2=\u003e5'::istore ? 2                   | t\nistore ?\u0026 integer[]   | does istore contain all specified keys?                               | '1=\u003e4,2=\u003e5'::istore ?\u0026 ARRAY[1,3]         | f\nistore ?\\| integer[]  | does istore contain any of the specified keys?                        | '1=\u003e4,2=\u003e5'::istore ?\\| ARRAY[1,3]        | t\nistore \\|\\| istore    | concatenate istores                                                   | '1=\u003e4, 2=\u003e5'::istore \\|\\| '3=\u003e4, 2=\u003e7'    | \"1\"=\u003e\"4\", \"2\"=\u003e\"7\", \"3\"=\u003e\"4\"\nistore + istore       | add value of matching keys (missing key will be treated as 0)         | '1=\u003e4,2=\u003e5'::istore + '1=\u003e4,3=\u003e6'::istore | \"1\"=\u003e\"8\", \"2\"=\u003e\"5\", \"3\"=\u003e\"6\"\nistore + integer      | add right operant to all values                                       | '1=\u003e4,2=\u003e5'::istore + 3                   | \"1\"=\u003e\"7\", \"2\"=\u003e\"8\"\nistore - istore       | subtract value of matching keys (missing key will be treated as 0)    | '1=\u003e4,2=\u003e5'::istore - '1=\u003e4,3=\u003e6'::istore | \"1\"=\u003e\"0\", \"2\"=\u003e\"5\", \"3\"=\u003e\"-6\"\nistore - integer      | subtract right operant to all values                                  | '1=\u003e4,2=\u003e5'::istore - 3                   | \"1\"=\u003e\"1\", \"2\"=\u003e\"2\"\nistore * istore       | multiply value of matching keys (missing key will be ignored)         | '1=\u003e4,2=\u003e5'::istore * '1=\u003e4,3=\u003e6'::istore | \"1\"=\u003e\"16\"\nistore * integer      | multiply right operant to all values                                  | '1=\u003e4,2=\u003e5'::istore * 3                   | \"1\"=\u003e\"12\", \"2\"=\u003e\"15\"\nistore / istore       | divide value of matching keys (missing key will be ignored)           | '1=\u003e4,2=\u003e5'::istore / '1=\u003e4,3=\u003e6'::istore | \"1\"=\u003e\"1\"\nistore / integer      | divide right operant to all values                                    | '1=\u003e4,2=\u003e5'::istore / 3                   | \"1\"=\u003e\"1\", \"2\"=\u003e\"1\"\n%% istore             | convert istore to array of alternating keys and values                | %% '1=\u003e4,2=\u003e5'::istore                    | {1,4,2,5}\n%# istore             | convert istore to two-dimensional key/value array                     | %# '1=\u003e4,2=\u003e5'::istore                    | {{1,4},{2,5}}\n\n### istore Functions\n\nFunction                                      | Return type               | Description                                                                 | Example                                                         | Result\n--------                                      | -----------               | -----------                                                                 | -------                                                         | ------\nexist(istore, integer)                        | boolean                   | does istore contain key?                                                    | exist('1=\u003e4,5=\u003e10'::istore, 5)                                  | t\nmin_key(istore)                               | integer                   | get the smallest key from an istore (NULL if not present)                   | min_key('1=\u003e4,5=\u003e10'::istore)                                   | 1\nmax_key(istore)                               | integer                   | get the biggest key from an istore (NULL if not present)                    | max_key('1=\u003e4,5=\u003e10'::istore)                                   | 5\nmax_value(istore)                             | integer                   | get the biggest value from an istore (NULL if not present)                  | max_value('1=\u003e4,5=\u003e10'::istore)                                 | 10\nfetchval(istore, integer)                     | integer                   | get value for key (NULL if not present)                                     | fetchval('1=\u003e4,5=\u003e10'::istore, 5)                               | 10\nakeys(istore)                                 | int[]                     | get istore's keys as an array                                               | akeys('1=\u003e3,2=\u003e4')                                              | {1,2}\navals(istore)                                 | int[]                     | get istore's values as an array                                             | avals('1=\u003e3,2=\u003e4')                                              | {3,4}\nskeys(istore)                                 | setof int                 | get istore's keys as a set                                                  | skeys('1=\u003e3,2=\u003e4')                                              | 1\u003cbr/\u003e2\nsvals(istore)                                 | setof int                 | get istore's values as a set                                                | svals('1=\u003e3,2=\u003e4')                                              | 3\u003cbr/\u003e4\nistore_to_json(istore)                        | json                      | get istore as a json value                                                  | istore_to_json('1=\u003e4,3=\u003e0,5=\u003e10'::istore)                       | {\"1\": 4, \"3\": 0, \"5\": 10}\ncompact(istore)                               | istore                    | remove `0` value keys                                                       | compact('1=\u003e4,3=\u003e0,5=\u003e10'::istore)                              | \"1\"=\u003e\"4\", \"5\"=\u003e\"10\"\nadd(istore, istore)                           | istore                    | add value of matching keys (missing key will be treated as 0)               | add('1=\u003e4,2=\u003e5'::istore, '1=\u003e4,3=\u003e6'::istore)                   | \"1\"=\u003e\"8\", \"2\"=\u003e\"5\", \"3\"=\u003e\"6\"\nadd(istore, integer)                          | istore                    | add right operant to all values                                             | add('1=\u003e4,2=\u003e5'::istore, '1=\u003e4,3=\u003e6'::istore)                   | \"1\"=\u003e\"8\", \"2\"=\u003e\"5\", \"3\"=\u003e\"6\"\nsubtract(istore, istore)                      | istore                    | subtract value of matching keys (missing key will be treated as 0)          | subtract('1=\u003e4,2=\u003e5'::istore, '1=\u003e4,3=\u003e6'::istore)              | \"1\"=\u003e\"0\", \"2\"=\u003e\"5\", \"3\"=\u003e\"-6\"\nsubtract(istore, integer)                     | istore                    | subtract right operant to all values                                        | subtract('1=\u003e4,2=\u003e5'::istore, '1=\u003e4,3=\u003e6'::istore)              | \"1\"=\u003e\"0\", \"2\"=\u003e\"5\", \"3\"=\u003e\"-6\"\nmultiply(istore, istore)                      | istore                    | multiply value of matching keys (missing key will be ignored)               | multiply('1=\u003e4,2=\u003e5'::istore, '1=\u003e4,3=\u003e6'::istore)              | \"1\"=\u003e\"16\"\nmultiply(istore, integer)                     | istore                    | multiply right operant to all values                                        | multiply('1=\u003e4,2=\u003e5'::istore, '1=\u003e4,3=\u003e6'::istore)              | \"1\"=\u003e\"16\"\ndivide(istore, istore)                        | istore                    | divide value of matching keys (missing key will be ignored)                 | divide('1=\u003e4,2=\u003e5'::istore, '1=\u003e4,3=\u003e6'::istore)                | \"1\"=\u003e\"1\"\ndivide(istore, integer)                       | istore                    | divide right operant to all values                                          | divide('1=\u003e4,2=\u003e5'::istore, '1=\u003e4,3=\u003e6'::istore)                | \"1\"=\u003e\"1\"\nistore(integer[])                             | istore                    | construct an istore from an array by counting elements                      | istore(ARRAY[1,2,1,3,2,2])                                      | \"1\"=\u003e\"2\", \"2\"=\u003e\"3\", \"3\"=\u003e\"1\"\nsum_up(istore)                                | bigint                    | sum values of an istore                                                     | sum_up('1=\u003e4,2=\u003e5'::istore)                                     |  9\nsum_up(istore, integer)                       | bigint                    | sum values of an istore up to a given key                                   | sum_up('1=\u003e4,2=\u003e5,3=\u003e5'::istore,2)                              |  9\nistore(integer[], integer[])                  | istore                    | construct an istore from separate key and value arrays                      | istore(ARRAY[1,2,3], ARRAY[4,5,6])                              | \"1\"=\u003e\"4\", \"2\"=\u003e\"5\", \"3\"=\u003e\"6\"\nfill_gaps(istore, integer, integer)           | istore                    | fill missing istore keys upto second parameter with third parameter         | fill_gaps('1=\u003e4,3=\u003e10'::istore,4,2)                             | \"0\"=\u003e\"2\", \"1\"=\u003e\"4\", \"2\"=\u003e\"2\", \"3\"=\u003e\"10\", \"4\"=\u003e\"2\"\naccumulate(istore)                            | istore                    | for each key calculate the rolling sum of values                            | accumulate('1=\u003e4,3=\u003e10'::istore)                                | \"1\"=\u003e\"4\", \"2\"=\u003e\"4\", \"3\"=\u003e\"14\"\naccumulate(istore, integer)                   | istore                    | for each key calculate the rolling sum of values upto second parameter      | accumulate('1=\u003e4,3=\u003e10'::istore, 4)                             | \"1\"=\u003e\"4\", \"2\"=\u003e\"4\", \"3\"=\u003e\"14\", \"4\"=\u003e\"14\"\nistore_seed(integer, integer, integer)        | istore                    | create an istore from first to second parameter with third parameter value  | istore_seed(2, 4, 5)                                            | \"2\"=\u003e\"5\", \"3\"=\u003e\"5\", \"4\"=\u003e\"5\"\nistore_val_larger(istore, istore)             | istore                    | merge istores with larger values                                            | istore_val_larger('1=\u003e4,2=\u003e5'::istore, '1=\u003e5,3=\u003e6'::istore)     | \"1\"=\u003e\"5\", \"2\"=\u003e\"5\", \"3\"=\u003e\"6\"\nistore_val_smaller(istore, istore)            | istore                    | merge istores with smaller values                                           | istore_val_smaller('1=\u003e4,2=\u003e5'::istore, '1=\u003e5,3=\u003e6'::istore)    | \"1\"=\u003e\"4\", \"2\"=\u003e\"5\", \"3\"=\u003e\"6\"\neach(istore)                                  | setof(key int, value int) | get istore's keys and values as a set                                       | each('1=\u003e4,5=\u003e10'::istore)                                      | key \\| value\u003cbr/\u003e ----+-------\u003cbr/\u003e 1 \\|     4\u003cbr/\u003e 5 \\|    10\nistore_to_json(istore)                        | integer[]                 | get istore's keys and values as json                                        | istore_to_json('1=\u003e4,2=\u003e5'::istore)                             |  {\"1\": 4, \"2\": 5}\nistore_to_array(istore)                       | integer[]                 | get istore's keys and values as an array of alternating keys and values     | istore_to_array('1=\u003e4,2=\u003e5'::istore)                            |  {1,4,2,5}\nistore_to_matrix(istore)                      | integer[]                 | get istore's keys and values as a two-dimensional array                     | istore_to_matrix('1=\u003e4,2=\u003e5'::istore)                           |  {{1,4},{2,5}}\nslice(istore, integer[])                      | istore                    | extract a subset of an istore                                               | slice('1=\u003e4,2=\u003e5'::istore, ARRAY[2])                            |  \"2\"=\u003e\"5\"\nslice(istore, min integer, max integer)       | istore                    | extract a subset of an istore where keys are between min and max            | slice('1=\u003e4,2=\u003e5,3=\u003e6,4=\u003e7'::istore, 2, 3)                      |  \"2\"=\u003e\"5\",\"3=\u003e6\"\nslice_array(istore, integer[])                | integer[]                 | extract a subset of an istore                                               | slice_array('1=\u003e4,2=\u003e5'::istore, ARRAY[2])                      |  {5}\nclamp_below(istore, integer)                  | istore                    | delete k/v pair up to a specified threshold and write their sum             | clamp_below('1=\u003e4,2=\u003e5,3=\u003e6'::istore, 2)                        |  \"2\"=\u003e\"9\",\"3\"=\u003e\"6\"\nclamp_above(istore, integer)                  | istore                    | delete k/v pair down to a specified threshold and write their sum           | clamp_above('1=\u003e4,2=\u003e5,3=\u003e6'::istore, 2)                        |  \"1\"=\u003e\"4\",\"2\"=\u003e\"11\"\ndelete(istore, integer)                       | istore                    | delete pair with matching key                                               | delete('1=\u003e4,2=\u003e5'::istore, 2)                                  |  \"1\"=\u003e\"4\"\ndelete(istore, integer[])                     | istore                    | delete pair with matching keys                                              | delete('1=\u003e4,2=\u003e5'::istore, ARRAY[2])                           |  \"1\"=\u003e\"4\"\nexists_all(istore, integer[])                 | boolean                   | does istore contain all specified keys?                                     | exists_all('1=\u003e4,2=\u003e5'::istore, ARRAY[2])                       |  t\nexists_any(istore, integer[])                 | boolean                   | does istore contain any of the specified keys?                              | exists_any('1=\u003e4,2=\u003e5'::istore, ARRAY[2])                       |  t\ndelete(istore, istore)                        | istore                    | delete matching pairs                                                       | delete('1=\u003e4,2=\u003e5'::istore, '1=\u003e3,2=\u003e5')                        |  \"1\"=\u003e\"4\"\nconcat(istore, istore)                        | istore                    | concat two istores                                                          | concat('1=\u003e4, 2=\u003e5'::istore, '3=\u003e4, 2=\u003e7'::istore)              |  \"1\"=\u003e\"4\", \"2\"=\u003e\"7\", \"3\"=\u003e\"4\"\nistore_in_range(istore, integer, integer)     | boolean                   | do istore values lie within the given (inclusive) range?                    | istore_in_range('-1=\u003e2, 10=\u003e17, 5=\u003e44'::istore, 0, 44)          | t\nistore_less_than(istore, integer)             | boolean                   | do istore values lie below the given value?                                 | istore_less_than('-1=\u003e2, 10=\u003e17, 5=\u003e44'::istore, 44)            | f\nistore_less_than_or_equal(istore, integer)    | boolean                   | do istore values lie below or equal to the given value?                     | istore_less_than_or_equal('-1=\u003e2, 10=\u003e17, 5=\u003e44'::istore, 44)   | t\nistore_greater_than(istore, integer)          | boolean                   | do istore values lie above the given value?                                 | istore_greater_than('-1=\u003e2, 10=\u003e17, 5=\u003e44'::istore, 2)          | f\nistore_greater_than_or_equa(istore, integer)  | boolean                   | do istore values lie above or equal to the given value?                     | istore_greater_than_or_equal('-1=\u003e2, 10=\u003e17, 5=\u003e44'::istore, 2) | t\nistore_floor(istore, integer)                 | istore                    | replace all values lower than the boundary with the boundary value          | istore_floor('-1=\u003e2, 10=\u003e17, 5=\u003e44'::istore, 20)                | \"-1\"=\u003e\"20\", \"10\"=\u003e\"20\", \"5\"=\u003e\"44\"\nistore_ceiling(istore, integer)               | istore                    | replace all values greater than the boundary with the boundary value        | istore_ceiling('-1=\u003e2, 10=\u003e17, 5=\u003e44'::istore, 20)              | \"-1\"=\u003e\"2\", \"10\"=\u003e\"17\", \"5\"=\u003e\"20\"\n\n\n### istore Aggregate Functions\n\nFunction        | Argument Type(s)    | Return Type           | Description\n---------       | ----------------    | -----------           | -----------\nsum(expression) | istore, bigistore   | bigistore             | sum of expression across all input values\nmin(expression) | istore, bigistore   | same as argument type | merge across all input values by selecting minimum keys value\nmax(expression) | istore, bigistore   | same as argument type | merge across all input values by selecting maximum keys value\n\n### Indexes\n\nistore has GIN index support for the ? operators For example:\n\n```\nCREATE INDEX hidx ON testistore USING GIN (i);\n```\n\n### Authors\n\nAlex Kliukin \u003calex.kliukin@adjust.com\u003e, Berlin, adjust GmbH, Germany\n\nIldar Musin \u003cildar@adjust.com\u003e, Berlin, adjust GmbH, Germany\n\nManuel Kniep \u003cmanuel@adjust.com\u003e, Berlin, adjust GmbH, Germany\n\nRobert Abraham \u003crobert@adust.com\u003e, Berlin, adjust GmbH, Germany\n\nGIN index support by Emre Hasegeli \u003cemre@hasegeli.com\u003e, Hamburg, Germany\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadjust%2Fistore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadjust%2Fistore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadjust%2Fistore/lists"}