json config for now; index create/delete
authorBill Erickson <berickxx@gmail.com>
Mon, 22 Oct 2018 20:46:04 +0000 (16:46 -0400)
committerBill Erickson <berickxx@gmail.com>
Wed, 28 Aug 2019 21:41:55 +0000 (17:41 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/examples/elastic-search-example.json [new file with mode: 0644]
Open-ILS/src/perlmods/lib/OpenILS/Utils/ElasticSearch.pm [new file with mode: 0644]
Open-ILS/src/sql/Pg/elastic-search.sql [deleted file]
Open-ILS/src/support-scripts/elastic-index.pl [new file with mode: 0755]

diff --git a/Open-ILS/examples/elastic-search-example.json b/Open-ILS/examples/elastic-search-example.json
new file mode 100644 (file)
index 0000000..fe35069
--- /dev/null
@@ -0,0 +1,474 @@
+{
+  "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']"
+    }
+  ]
+}
+
diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Utils/ElasticSearch.pm b/Open-ILS/src/perlmods/lib/OpenILS/Utils/ElasticSearch.pm
new file mode 100644 (file)
index 0000000..34f2bf8
--- /dev/null
@@ -0,0 +1,149 @@
+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;
+
+
diff --git a/Open-ILS/src/sql/Pg/elastic-search.sql b/Open-ILS/src/sql/Pg/elastic-search.sql
deleted file mode 100644 (file)
index 1c2f2b4..0000000
+++ /dev/null
@@ -1,119 +0,0 @@
-
-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;
-
-*/
diff --git a/Open-ILS/src/support-scripts/elastic-index.pl b/Open-ILS/src/support-scripts/elastic-index.pl
new file mode 100755 (executable)
index 0000000..e604513
--- /dev/null
@@ -0,0 +1,41 @@
+#!/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;
+
+