--- /dev/null
+{
+ "comment": "Elastic Search Configuration",
+ "version": 0.1,
+ "clusters": {
+ "main": {
+ "nodes": [
+ "http://localhost:9200"
+ ]
+ }
+ },
+ "indexes": {
+ "bib-search": {
+ "cluster": "main",
+ "document-type": "bib-record",
+ "lang_analyzer": "english",
+ "settings": {
+ "number_of_shards": 5,
+ "analysis": {
+ "analyzer": {
+ "folding": {
+ "filter": ["lowercase", "asciifolding"],
+ "tokenizer": "standard"
+ }
+ }
+ }
+ },
+ "base-properties": {
+ "source": {
+ "type": "integer",
+ "index": "false"
+ },
+ "create_date": {
+ "type": "date",
+ "index": "false"
+ },
+ "edit_date": {
+ "type": "date",
+ "index": "false"
+ },
+ "title": {
+ "type": "text",
+ "analyzer": "english",
+ "fields": {
+ "folded": {
+ "type": "text",
+ "analyzer": "folding"
+ },
+ "raw": {
+ "type": "keyword"
+ }
+ }
+ },
+ "author": {
+ "type": "text",
+ "analyzer": "english",
+ "fields": {
+ "folded": {
+ "type": "text",
+ "analyzer": "folding"
+ },
+ "raw": {
+ "type": "keyword"
+ }
+ }
+ },
+ "subject": {
+ "type": "text",
+ "analyzer": "english",
+ "fields": {
+ "folded": {
+ "type": "text",
+ "analyzer": "folding"
+ },
+ "raw": {
+ "type": "keyword"
+ }
+ }
+ },
+ "series": {
+ "type": "text",
+ "analyzer": "english",
+ "fields": {
+ "folded": {
+ "type": "text",
+ "analyzer": "folding"
+ },
+ "raw": {
+ "type": "keyword"
+ }
+ }
+ },
+ "keyword": {
+ "type": "text",
+ "analyzer": "english",
+ "fields": {
+ "folded": {
+ "type": "text",
+ "analyzer": "folding"
+ }
+ }
+ },
+ "identifier": {
+ "type": "keyword",
+ "index": "false"
+ },
+ "holdings": {
+ "type": "nested",
+ "properties": {
+ "status": {
+ "type": "integer",
+ "index": "false"
+ },
+ "circ_lib": {
+ "type": "integer",
+ "index": "false"
+ },
+ "location": {
+ "type": "integer",
+ "index": "false"
+ },
+ "circulate": {
+ "type": "boolean",
+ "index": "false"
+ },
+ "opac_visible": {
+ "type": "boolean",
+ "index": "false"
+ }
+ }
+ }
+ }
+ }
+ },
+ "dynamic-properties": [
+ {
+ "index": "bib-search",
+ "field_class": "title",
+ "name" : "abbreviated",
+ "label" : "Abbreviated Title",
+ "format" : "mods32",
+ "xpath" : "//mods32:mods/mods32:titleInfo[mods32:title and (@type='abbreviated')]"
+ }, {
+ "index": "bib-search",
+ "field_class": "title",
+ "name": "translated",
+ "label": "Translated Title",
+ "format": "mods32",
+ "xpath": "//mods32:mods/mods32:titleInfo[mods32:title and (@type='translated-nfi')]"
+ }, {
+ "index": "bib-search",
+ "field_class": "title",
+ "name": "alternative",
+ "label": "Alternate Title",
+ "format": "mods32",
+ "xpath": "//mods32:mods/mods32:titleInfo[mods32:title and starts-with(@type,'alternative')]"
+ }, {
+ "index": "bib-search",
+ "field_class": "title",
+ "name": "uniform",
+ "label": "Uniform Title",
+ "format": "mods32",
+ "xpath": "//mods32:mods/mods32:titleInfo[mods32:title and (@type='uniform-nfi')]"
+ }, {
+ "index": "bib-search",
+ "field_class": "title",
+ "name": "proper",
+ "label": "Title Proper",
+ "format": "mods32",
+ "xpath": "//mods32:mods/mods32:titleNonfiling[mods32:title and not (@type)]"
+ }, {
+ "index": "bib-search",
+ "field_class": "author",
+ "name": "corporate",
+ "label": "Corporate Author",
+ "format": "mods32",
+ "xpath": "//mods32:mods/mods32:name[@type='corporate' and (mods32:role/mods32:roleTerm[text()='creator'] or mods32:role/mods32:roleTerm[text()='aut'] or mods32:role/mods32:roleTerm[text()='cre'])]//*[local-name()='namePart']"
+ }, {
+ "index": "bib-search",
+ "field_class": "author",
+ "name": "personal",
+ "label": "Personal Author",
+ "format": "mods32",
+ "xpath": "//mods32:mods/mods32:name[@type='personal' and mods32:role/mods32:roleTerm[text()='creator']]//*[local-name()='namePart']"
+ }, {
+ "index": "bib-search",
+ "field_class": "author",
+ "name": "conference",
+ "label": "Conference Author",
+ "format": "mods32",
+ "xpath": "//mods32:mods/mods32:name[@type='conference' and mods32:role/mods32:roleTerm[text()='creator']]//*[local-name()='namePart']"
+ }, {
+ "index": "bib-search",
+ "field_class": "author",
+ "name": "other",
+ "label": "Other Author",
+ "format": "mods32",
+ "xpath": "//mods32:mods/mods32:name[@type='personal' and not(mods32:role/mods32:roleTerm[text()='creator'])]//*[local-name()='namePart']"
+ }, {
+ "index": "bib-search",
+ "field_class": "keyword",
+ "name": "keyword",
+ "label": "General Keywords",
+ "format": "mods32",
+ "xpath": "//mods32:mods/*[not(local-name()='originInfo')]"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "accession",
+ "label": "Accession Number",
+ "format": "marcxml",
+ "xpath": "//marc:controlfield[@tag='001']"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "isbn",
+ "label": "ISBN",
+ "format": "marcxml",
+ "xpath": "//marc:datafield[@tag='020']/marc:subfield[@code='a' or @code='z']"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "ismn",
+ "label": "ISMN",
+ "format": "marcxml",
+ "xpath": "//marc:datafield[@tag='024' and @ind1='2']/marc:subfield[@code='a' or @code='z']"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "ean",
+ "label": "EAN",
+ "format": "marcxml",
+ "xpath": "//marc:datafield[@tag='024' and @ind1='3']/marc:subfield[@code='a' or @code='z']"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "isrc",
+ "label": "ISRC",
+ "format": "marcxml",
+ "xpath": "//marc:datafield[@tag='024' and @ind1='0']/marc:subfield[@code='a' or @code='z']"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "sici",
+ "label": "SICI",
+ "format": "marcxml",
+ "xpath": "//marc:datafield[@tag='024' and @ind1='4']/marc:subfield[@code='a' or @code='z']"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "bibcn",
+ "label": "Local Free-Text Call Number",
+ "format": "marcxml",
+ "xpath": "//marc:datafield[@tag='099']"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "bibid",
+ "label": "Internal ID",
+ "format": "marcxml",
+ "xpath": "//marc:datafield[@tag='901']/marc:subfield[@code='c']"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "scn",
+ "label": "System Control Number",
+ "format": "marcxml",
+ "xpath": "//marc:datafield[@tag='035']/marc:subfield[@code='a']"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "lccn",
+ "label": "LC Control Number",
+ "format": "marcxml",
+ "xpath": "//marc:datafield[@tag='010']/marc:subfield[@code='a' or @code='z']"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "genre",
+ "label": "Genre",
+ "format": "marcxml",
+ "xpath": "//marc:datafield[@tag='655']//*[local-name()='subfield' and contains('abvxyz',@code)]"
+ }, {
+ "index": "bib-search",
+ "field_class": "subject",
+ "name": "complete",
+ "label": "All Subjects",
+ "format": "mods32",
+ "xpath": "//mods32:mods/mods32:subject[not(descendant::mods32:geographicCode)]"
+ }, {
+ "index": "bib-search",
+ "field_class": "author",
+ "name": "creator",
+ "label": "All Creators",
+ "format": "mods32",
+ "xpath": "//mods32:mods/mods32:name[mods32:role/mods32:roleTerm[text()='creator']]//*[local-name()='namePart']"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "edition",
+ "label": "Edition",
+ "format": "mods33",
+ "xpath": "//mods33:mods/mods33:originInfo//mods33:edition[1]"
+ }, {
+ "index": "bib-search",
+ "field_class": "series",
+ "name": "seriestitle",
+ "label": "Series Title",
+ "format": "mods32",
+ "xpath": "//mods32:mods/mods32:relatedItem[@type='series']/mods32:titleInfo[not(@type='nfi')]"
+ }, {
+ "index": "bib-search",
+ "field_class": "subject",
+ "name": "name",
+ "label": "Name Subject",
+ "format": "mods32",
+ "xpath": "//mods32:mods/mods32:subject/mods32:name//*[local-name()='namePart']"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "issn",
+ "label": "ISSN",
+ "format": "marcxml",
+ "xpath": "//marc:datafield[@tag='022']/marc:subfield[@code='a' or @code='z']"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "upc",
+ "label": "UPC",
+ "format": "marcxml",
+ "xpath": "//marc:datafield[@tag='024' and @ind1='1']/marc:subfield[@code='a' or @code='z']"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "tcn",
+ "label": "Title Control Number",
+ "format": "marcxml",
+ "xpath": "//marc:datafield[@tag='901']/marc:subfield[@code='a']"
+ }, {
+ "index": "bib-search",
+ "field_class": "subject",
+ "name": "geographic",
+ "label": "Geographic Subject",
+ "format": "mods32",
+ "xpath": "//mods32:mods/mods32:subject/mods32:geographic"
+ }, {
+ "index": "bib-search",
+ "field_class": "subject",
+ "name": "temporal",
+ "label": "Temporal Subject",
+ "format": "mods32",
+ "xpath": "//mods32:mods/mods32:subject/mods32:temporal"
+ }, {
+ "index": "bib-search",
+ "field_class": "keyword",
+ "name": "physical_description",
+ "label": "Physical Descrption",
+ "format": "mods33",
+ "xpath": "(//mods33:mods/mods33:physicalDescription/mods33:form://mods33:mods/mods33:physicalDescription/mods33:extent://mods33:mods/mods33:physicalDescription/mods33:reformattingQuality://mods33:mods/mods33:physicalDescription/mods33:internetMediaType://mods33:mods/mods33:physicalDescription/mods33:digitalOrigin)"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "publisher",
+ "label": "Publisher",
+ "format": "mods33",
+ "xpath": "//mods33:mods/mods33:originInfo//mods33:publisher[1]"
+ }, {
+ "index": "bib-search",
+ "field_class": "keyword",
+ "name": "abstract",
+ "label": "Abstract",
+ "format": "mods33",
+ "xpath": "//mods33:mods/mods33:abstract"
+ }, {
+ "index": "bib-search",
+ "field_class": "keyword",
+ "name": "toc",
+ "label": "Table of Contents",
+ "format": "mods33",
+ "xpath": "//mods33:tableOfContents"
+ }, {
+ "index": "bib-search",
+ "field_class": "keyword",
+ "name": "bibliography",
+ "label": "Bibliography",
+ "format": "mods33",
+ "xpath": "//mods33:note[@type='bibliography']"
+ }, {
+ "index": "bib-search",
+ "field_class": "keyword",
+ "name": "thesis",
+ "label": "Thesis",
+ "format": "mods33",
+ "xpath": "//mods33:note[@type='thesis']"
+ }, {
+ "index": "bib-search",
+ "field_class": "keyword",
+ "name": "production_credits",
+ "label": "Creation/Production Credits",
+ "format": "mods33",
+ "xpath": "//mods33:note[@type='creation/production credits']"
+ }, {
+ "index": "bib-search",
+ "field_class": "keyword",
+ "name": "performers",
+ "label": "Performers",
+ "format": "mods33",
+ "xpath": "//mods33:note[@type='performers']"
+ }, {
+ "index": "bib-search",
+ "field_class": "keyword",
+ "name": "general_note",
+ "label": "General Note",
+ "format": "mods33",
+ "xpath": "//mods33:note[not(@type)]"
+ }, {
+ "index": "bib-search",
+ "field_class": "author",
+ "name": "first_author",
+ "label": "Author",
+ "format": "mods32",
+ "xpath": "//mods32:mods/mods32:name[mods32:role/mods32:roleTerm[text()='creator']][1]//*[local-name()='namePart']"
+ }, {
+ "index": "bib-search",
+ "field_class": "title",
+ "name": "maintitle",
+ "label": "Main Title",
+ "format": "marcxml",
+ "xpath": "//*[@tag='245']/*[@code='a']"
+ }, {
+ "index": "bib-search",
+ "field_class": "subject",
+ "name": "topic",
+ "label": "Topic Subject",
+ "format": "mods32",
+ "xpath": "//mods32:mods/mods32:subject/mods32:topic"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "item_lang",
+ "label": "Language",
+ "format": "marcxml",
+ "xpath": "substring(//marc:controlfield[@tag='008']/text(), '36', '38')"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "item_form",
+ "label": "Item Form",
+ "format": "marcxml",
+ "xpath": "substring(//marc:controlfield[@tag='008']/text(), '24', '25')"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "audience",
+ "label": "Audience",
+ "format": "marcxml",
+ "xpath": "substring(//marc:controlfield[@tag='008']/text(), '23', '24')"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "lit_form",
+ "label": "Literary Form",
+ "format": "marcxml",
+ "xpath": "substring(//marc:controlfield[@tag='008']/text(), '34', '35')"
+ }, {
+ "index": "bib-search",
+ "field_class": "identifier",
+ "name": "pub_date",
+ "label": "Publication Date",
+ "format": "mods32",
+ "xpath": "//mods32:mods/mods32:originInfo/mods32:dateIssued[@encoding='marc']"
+ }
+ ]
+}
+
--- /dev/null
+package OpenILS::Utils::ElasticSearch;
+# ---------------------------------------------------------------
+# Copyright (C) 2018 King County Library System
+# Author: Bill Erickson <berickxx@gmail.com>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+# ---------------------------------------------------------------
+use strict;
+use warnings;
+use Clone qw/clone/;
+use DateTime;
+use OpenSRF::Utils::JSON;
+use OpenSRF::Utils::Logger qw/:logger/;
+use OpenILS::Utils::DateTime qw/:datetime/;
+#use OpenILS::Utils::CStoreEditor qw/:funcs/;
+use Search::Elasticsearch;
+
+our $date_parser = DateTime::Format::ISO8601->new;
+
+sub new {
+ my ($class, %args) = @_;
+ my $self = bless(
+ { clusters => {},
+ config_file => $args{config_file}
+ }, $class
+ );
+ $self->read_config;
+ return $self;
+}
+
+sub read_config {
+ my $self = shift;
+
+ open(CONFIG_FILE, $self->{config_file})
+ or die "Cannot open elastic config " .$self->{config_file}. ": $!\n";
+
+ my $json = join('', <CONFIG_FILE>);
+
+ $self->{config} = OpenSRF::Utils::JSON->JSON2perl($json);
+
+ close(CONFIG_FILE);
+}
+
+sub connect {
+ my ($self, $cluster) = @_;
+
+ my $nodes = $self->{config}{clusters}{$cluster}{nodes};
+
+ $logger->info("ES connecting to nodes @$nodes");
+
+ $self->{clusters}{$cluster} = {
+ es => Search::Elasticsearch->new(nodes => $nodes)
+ };
+}
+
+sub es {
+ my ($self, $cluster) = @_;
+ return $self->{clusters}{$cluster}{es};
+}
+
+sub delete_index {
+ my ($self, $cluster, $index) = @_;
+
+ if ($self->es($cluster)->indices->exists(index => $index)) {
+ $logger->info("ES deleting index '$index' on cluster '$cluster'");
+ $self->es($cluster)->indices->delete(index => $index);
+ } else {
+ $logger->warn("ES index '$index' does not exist");
+ }
+}
+
+sub create_index {
+ my ($self, $cluster, $index) = @_;
+
+ if ($self->es($cluster)->indices->exists(index => $index)) {
+ $logger->warn("ES index '$index' already exists");
+ return;
+ }
+
+ $logger->info("ES creating index '$index' on cluster '$cluster'");
+
+ my $config = $self->{config};
+
+ my $es = $self->es($cluster);
+ my $mappings = $config->{indexes}{$index}{'base-properties'};
+
+ # TODO: a dynamic property may live in multiple indexes
+ my @dynamics = grep {$_->{index} eq $index}
+ @{$config->{'dynamic-properties'}};
+
+ # Add an index definition for each dynamic field.
+ # Add copy_to for field_class-level combined searches.
+ for my $prop (@dynamics) {
+ my $field_class = $prop->{field_class};
+ my $field_name = "$field_class|" . $prop->{name};
+
+ # Clone the class-level index definition (e.g. title) to
+ # use as the source of the field-specific index.
+ my $def = clone($config->{indexes}{$index}{'base-properties'}{$field_class});
+
+ # Copy data for all fields to their parent class to
+ # support group-level searches (e.g. title search)
+ $def->{copy_to} = $field_class;
+ $mappings->{$field_name} = $def;
+ }
+
+ my $settings = $config->{indexes}{$index}{settings};
+ $settings->{number_of_replicas} =
+ scalar(@{$config->{clusters}{$cluster}{nodes}});
+
+ my $doc_type = $config->{indexes}{$index}{'document-type'};
+
+ my $conf = {
+ index => $index,
+ body => {
+ settings => $settings,
+ mappings => {$doc_type => {properties => $mappings}}
+ }
+ };
+
+ # send to es
+
+
+ #print "\n\n\n";
+ #print OpenSRF::Utils::JSON->perl2JSON($conf) . "\n";
+ #print "\n\n\n";
+
+ eval { $self->es($cluster)->indices->create($conf) };
+
+ if ($@) {
+ my $msg =
+ "ES failed to create index cluster=$cluster index=$index error=$@";
+ $logger->error($msg);
+ die "$msg\n";
+ }
+}
+
+
+1;
+
+
+++ /dev/null
-
-BEGIN;
-
-CREATE TABLE config.elastic_cluster (
- id SERIAL PRIMARY KEY,
- label TEXT NOT NULL
-);
-
-CREATE TABLE config.elastic_server (
- id SERIAL PRIMARY KEY,
- label TEXT NOT NULL UNIQUE,
- host TEXT NOT NULL,
- proto TEXT NOT NULL,
- port INTEGER NOT NULL,
- active BOOLEAN NOT NULL DEFAULT FALSE,
- cluster INTEGER NOT NULL REFERENCES config.elastic_cluster (id)
-);
-
-CREATE TABLE config.elastic_index (
- id SERIAL PRIMARY KEY,
- name TEXT NOT NULL UNIQUE,
- purpose TEXT NOT NULL DEFAULT 'bib-search', -- constraint?
- active BOOLEAN NOT NULL DEFAULT FALSE,
- cluster INTEGER NOT NULL REFERENCES config.elastic_cluster (id)
-);
-
-CREATE TABLE config.elastic_field (
- id SERIAL PRIMARY KEY,
- elastic_index INTEGER NOT NULL REFERENCES config.elastic_index (id),
- active BOOLEAN NOT NULL DEFAULT FALSE,
- field_class TEXT NOT NULL REFERENCES config.metabib_class (name),
- label TEXT NOT NULL,
- name TEXT NOT NULL,
- weight INTEGER NOT NULL DEFAULT 1,
- format TEXT NOT NULL REFERENCES config.xml_transform (name),
- xpath TEXT NOT NULL,
- search_field BOOLEAN NOT NULL DEFAULT FALSE,
- facet_field BOOLEAN NOT NULL DEFAULT FALSE,
- sort_field BOOLEAN NOT NULL DEFAULT FALSE,
- multi_value BOOLEAN NOT NULL DEFAULT FALSE
-);
-
-/* SEED DATA ------------------------------------------------------------ */
-
-
-INSERT INTO config.elastic_cluster (label) VALUES ('Main');
-
-INSERT INTO config.elastic_server
- (label, host, proto, port, active, cluster)
-VALUES ('localhost', 'localhost', 'http', 9200, TRUE,
- (SELECT id FROM config.elastic_cluster WHERE label = 'Main'));
-
-INSERT INTO config.elastic_index (name, purpose, active, cluster)
-VALUES ('Bib Search', 'bib-search', TRUE,
- (SELECT id FROM config.elastic_cluster WHERE label = 'Main'));
-
--- Start with indexes that match search/facet fields in config.metabib_field
-
-INSERT INTO config.elastic_field
- (elastic_index, active, field_class, label, name, weight, format,
- xpath, search_field, facet_field)
-SELECT
- (SELECT id FROM config.elastic_index WHERE purpose = 'bib-search'),
- TRUE,
- cmf.field_class,
- cmf.label,
- cmf.name,
- cmf.weight,
- cmf.format,
- cmf.xpath || COALESCE(cmf.facet_xpath, COALESCE(cmf.display_xpath, '')),
- cmf.search_field,
- cmf.facet_field
-FROM config.metabib_field cmf
-WHERE cmf.xpath IS NOT NULL AND (cmf.search_field OR cmf.facet_field);
-
--- Add additional indexes for other search-y / filter-y stuff
-
-INSERT INTO config.elastic_field
- (elastic_index, active, field_class, label, name, format,
- search_field, facet_field, xpath)
-VALUES (
- (SELECT id FROM config.elastic_index WHERE purpose = 'bib-search'),
- TRUE,
- 'identifier', 'Language', 'item_lang', 'marcxml', TRUE, TRUE,
- $$substring(//marc:controlfield[@tag='008']/text(), '36', '38')$$
-), (
- (SELECT id FROM config.elastic_index WHERE purpose = 'bib-search'),
- TRUE,
- 'identifier', 'Item Form', 'item_form', 'marcxml', TRUE, TRUE,
- $$substring(//marc:controlfield[@tag='008']/text(), '24', '25')$$
-), (
- (SELECT id FROM config.elastic_index WHERE purpose = 'bib-search'),
- TRUE,
- 'identifier', 'Audience', 'audience', 'marcxml', TRUE, TRUE,
- $$substring(//marc:controlfield[@tag='008']/text(), '23', '24')$$
-), (
- (SELECT id FROM config.elastic_index WHERE purpose = 'bib-search'),
- TRUE,
- 'identifier', 'Literary Form', 'lit_form', 'marcxml', TRUE, TRUE,
- $$substring(//marc:controlfield[@tag='008']/text(), '34', '35')$$
-), (
- (SELECT id FROM config.elastic_index WHERE purpose = 'bib-search'),
- TRUE,
- 'identifier', 'Publication Date', 'pub_date', 'mods32', TRUE, TRUE,
- $$//mods32:mods/mods32:originInfo/mods32:dateIssued[@encoding='marc']$$
-);
-
--- TODO ADD MORE FIELDS
-
-COMMIT;
-
-/* UNDO
-
-DROP TABLE config.elastic_field;
-DROP TABLE config.elastic_index;
-DROP TABLE config.elastic_server;
-DROP TABLE config.elastic_cluster;
-
-*/
--- /dev/null
+#!/usr/bin/perl
+use strict;
+use warnings;
+use Getopt::Long;
+use OpenILS::Utils::ElasticSearch;
+
+my $help;
+my $elastic_config;
+my $cluster = 'main';
+my $create_index;
+my $delete_index;
+my $index_name; # use "all" to affect all configured indexes
+my $populate;
+my $partial;
+
+GetOptions(
+ 'help' => \$help,
+ 'elastic-config=s' => \$elastic_config,
+ 'cluster=s' => \$cluster,
+ 'create-index' => \$create_index,
+ 'delete-index' => \$delete_index,
+ 'index=s' => \$index_name,
+ 'populate' => \$populate,
+ 'partial' => \$partial
+) || die "\nSee --help for more\n";
+
+
+my $es = OpenILS::Utils::ElasticSearch->new(
+ cluster => $cluster,
+ config_file => $elastic_config
+);
+
+$es->connect($cluster);
+
+$es->delete_index($cluster, $index_name) if $delete_index;
+
+$es->create_index($cluster, $index_name) if $create_index;
+
+$es->populate_index($cluster, $index_name) if $populate;
+
+