I noticed that some of the slowest cases in Joel's regex test corpus had issues with back-reference matching, and along the way to fixing that discovered what seems to me to be a bug in the engine's handling of back-references. To wit, what should happen if a back-reference is to a subexpression that contains constraints? A simple example is
SELECT regexp_match('foof', '(^f)o*\1'); To my mind, the back reference is only chartered to match the literal characters matched by the referenced subexpression. Here, since that expression matches "f", the backref should too, and thus we should get a match to "foof". Perl gives that answer, anyway; but our existing code says there's no match. That's because it effectively copies the constraints within the referenced subexpression, in addition to making the data comparison. The "^" can't match where the second "f" is, so we lose. 0001 attached fixes this by stripping constraint arcs out of the NFA that's applied to the backref subre tree node. Now, as to the performance issue ... if you load up the data in "trouble.sql" attached, and do SELECT regexp_matches(subject, pattern, 'g') FROM trouble; you'll be waiting a good long time, even with our recent improvements. (Up to now I hadn't tried the 'g' flag with Joel's test cases, so I hadn't noticed what a problem this particular example has got.) The reason for the issue is that the pattern is (["'`])(?:\\\1|.)*?\1 and the subject string has a mix of " and ' quote characters. As currently implemented, our engine tries to resolve the match at any substring ending in either " or ', since the NFA created for the backref can match either. That leads to O(N^2) time wasted trying to verify wrong matches. I realized that this could be improved by replacing the NFA/DFA match step for a backref node with a string literal match, if the backreference match string is already known at the time we try to apply the NFA/DFA. That's not a panacea, but it helps in most simple cases including this one. The way to visualize what is happening is that we have a tree of binary concatenation nodes: concat / \ capture concat / \ other stuff backref Each concat node performs fast NFA/DFA checks on both its children before recursing to the children to make slow exact checks. When we recurse to the capture node, it records the actual match substring, so now we know whether the capture is " or '. Then, when we recurse to the lower concat node, the capture is available while it makes NFA/DFA checks for its two children; so it will never mistakenly guess that its second child matches a substring it doesn't, and thus it won't try to do exact checking of the "other stuff" on a match that's bound to fail later. So this works as long as the tree of concat nodes is right-deep, which fortunately is the normal case. It won't help if we have a left-deep tree: concat / \ concat backref / \ capture other stuff because the upper concat node will do its NFA/DFA check on the backref node before recursing to its left child, where the capture will occur. But to get that tree, you have to have written extra parentheses: ((capture)otherstuff)\2 I don't see a way to improve that situation, unless perhaps with massive rejiggering of the regex execution engine. But 0002 attached does help a lot in the simple case. (BTW, the connection between 0001 and 0002 is that if we want to keep the existing semantics that a backref enforces constraints, 0002 doesn't work, since it won't do that.) regards, tom lane
diff --git a/doc/src/sgml/func.sgml b/doc/src/sgml/func.sgml index 08f08322ca..6c189bfed2 100644 --- a/doc/src/sgml/func.sgml +++ b/doc/src/sgml/func.sgml @@ -6166,6 +6166,9 @@ SELECT foo FROM regexp_split_to_table('the quick brown fox', '\s*') AS foo; The subexpression must entirely precede the back reference in the RE. Subexpressions are numbered in the order of their leading parentheses. Non-capturing parentheses do not define subexpressions. + The back reference considers only the string characters matched by the + referenced subexpression, not any constraints contained in it. For + example, <literal>(^\d)\1</literal> will match <literal>22</literal>. </para> <table id="posix-character-entry-escapes-table"> diff --git a/src/backend/regex/regc_nfa.c b/src/backend/regex/regc_nfa.c index a10a346e8f..77b860cb0f 100644 --- a/src/backend/regex/regc_nfa.c +++ b/src/backend/regex/regc_nfa.c @@ -1382,6 +1382,77 @@ duptraverse(struct nfa *nfa, } } +/* + * removeconstraints - remove any constraints in an NFA + * + * Constraint arcs are replaced by empty arcs, essentially treating all + * constraints as automatically satisfied. + */ +static void +removeconstraints(struct nfa *nfa, + struct state *start, /* process subNFA starting here */ + struct state *stop) /* and stopping here */ +{ + if (start == stop) + return; + + stop->tmp = stop; + removetraverse(nfa, start); + /* done, except for clearing out the tmp pointers */ + + stop->tmp = NULL; + cleartraverse(nfa, start); +} + +/* + * removetraverse - recursive heart of removeconstraints + */ +static void +removetraverse(struct nfa *nfa, + struct state *s) +{ + struct arc *a; + struct arc *oa; + + /* Since this is recursive, it could be driven to stack overflow */ + if (STACK_TOO_DEEP(nfa->v->re)) + { + NERR(REG_ETOOBIG); + return; + } + + if (s->tmp != NULL) + return; /* already done */ + + s->tmp = s; + for (a = s->outs; a != NULL && !NISERR(); a = oa) + { + removetraverse(nfa, a->to); + if (NISERR()) + break; + oa = a->outchain; + switch (a->type) + { + case PLAIN: + case EMPTY: + /* nothing to do */ + break; + case AHEAD: + case BEHIND: + case '^': + case '$': + case LACON: + /* replace it */ + newarc(nfa, EMPTY, 0, s, a->to); + freearc(nfa, a); + break; + default: + NERR(REG_ASSERT); + break; + } + } +} + /* * cleartraverse - recursive cleanup for algorithms that leave tmp ptrs set */ diff --git a/src/backend/regex/regcomp.c b/src/backend/regex/regcomp.c index 1f7fa513b2..3c7627a955 100644 --- a/src/backend/regex/regcomp.c +++ b/src/backend/regex/regcomp.c @@ -150,6 +150,8 @@ static void delsub(struct nfa *, struct state *, struct state *); static void deltraverse(struct nfa *, struct state *, struct state *); static void dupnfa(struct nfa *, struct state *, struct state *, struct state *, struct state *); static void duptraverse(struct nfa *, struct state *, struct state *); +static void removeconstraints(struct nfa *, struct state *, struct state *); +static void removetraverse(struct nfa *, struct state *); static void cleartraverse(struct nfa *, struct state *); static struct state *single_color_transition(struct state *, struct state *); static void specialcolors(struct nfa *); @@ -1182,6 +1184,10 @@ parseqatom(struct vars *v, dupnfa(v->nfa, v->subs[subno]->begin, v->subs[subno]->end, atom->begin, atom->end); NOERR(); + + /* The backref node's NFA should not enforce any constraints */ + removeconstraints(v->nfa, atom->begin, atom->end); + NOERR(); } /* diff --git a/src/test/modules/test_regex/expected/test_regex.out b/src/test/modules/test_regex/expected/test_regex.out index 5d993f40c2..01d50ec1e3 100644 --- a/src/test/modules/test_regex/expected/test_regex.out +++ b/src/test/modules/test_regex/expected/test_regex.out @@ -2636,6 +2636,28 @@ select * from test_regex('^(.+)( \1)+$', 'abc abc abd', 'RP'); {2,REG_UBACKREF,REG_UNONPOSIX} (1 row) +-- back reference only matches the string, not any constraints +select * from test_regex('(^\w+).*\1', 'abc abc abc', 'LRP'); + test_regex +-------------------------------------------- + {1,REG_UBACKREF,REG_UNONPOSIX,REG_ULOCALE} + {"abc abc abc",abc} +(2 rows) + +select * from test_regex('(^\w+\M).*\1', 'abc abcd abd', 'LRP'); + test_regex +-------------------------------------------- + {1,REG_UBACKREF,REG_UNONPOSIX,REG_ULOCALE} + {"abc abc",abc} +(2 rows) + +select * from test_regex('(\w+(?= )).*\1', 'abc abcd abd', 'HLRP'); + test_regex +------------------------------------------------------------ + {1,REG_UBACKREF,REG_ULOOKAROUND,REG_UNONPOSIX,REG_ULOCALE} + {"abc abc",abc} +(2 rows) + -- doing 15 "octal escapes vs back references" -- # initial zero is always octal -- expectMatch 15.1 MP "a\\010b" "a\bb" "a\bb" diff --git a/src/test/modules/test_regex/sql/test_regex.sql b/src/test/modules/test_regex/sql/test_regex.sql index b99329391e..7f5bc6e418 100644 --- a/src/test/modules/test_regex/sql/test_regex.sql +++ b/src/test/modules/test_regex/sql/test_regex.sql @@ -770,6 +770,11 @@ select * from test_regex('^(.+)( \1)+$', 'abc abd abc', 'RP'); -- expectNomatch 14.29 RP {^(.+)( \1)+$} {abc abc abd} select * from test_regex('^(.+)( \1)+$', 'abc abc abd', 'RP'); +-- back reference only matches the string, not any constraints +select * from test_regex('(^\w+).*\1', 'abc abc abc', 'LRP'); +select * from test_regex('(^\w+\M).*\1', 'abc abcd abd', 'LRP'); +select * from test_regex('(\w+(?= )).*\1', 'abc abcd abd', 'HLRP'); + -- doing 15 "octal escapes vs back references" -- # initial zero is always octal
diff --git a/src/backend/regex/rege_dfa.c b/src/backend/regex/rege_dfa.c index 1db52d1005..18f87c69d4 100644 --- a/src/backend/regex/rege_dfa.c +++ b/src/backend/regex/rege_dfa.c @@ -58,6 +58,19 @@ longest(struct vars *v, if (hitstopp != NULL) *hitstopp = 0; + /* if this is a backref to a known string, just match against that */ + if (d->backno >= 0) + { + assert((size_t) d->backno < v->nmatch); + if (v->pmatch[d->backno].rm_so >= 0) + { + cp = dfa_backref(v, d, start, start, stop, false); + if (cp == v->stop && stop == v->stop && hitstopp != NULL) + *hitstopp = 1; + return cp; + } + } + /* fast path for matchall NFAs */ if (d->cnfa->flags & MATCHALL) { @@ -210,6 +223,20 @@ shortest(struct vars *v, if (hitstopp != NULL) *hitstopp = 0; + /* if this is a backref to a known string, just match against that */ + if (d->backno >= 0) + { + assert((size_t) d->backno < v->nmatch); + if (v->pmatch[d->backno].rm_so >= 0) + { + cp = dfa_backref(v, d, start, min, max, true); + if (cp != NULL && coldp != NULL) + *coldp = start; + /* there is no case where we should set *hitstopp */ + return cp; + } + } + /* fast path for matchall NFAs */ if (d->cnfa->flags & MATCHALL) { @@ -467,6 +494,94 @@ matchuntil(struct vars *v, return 1; } +/* + * dfa_backref - find best match length for a known backref string + * + * When the backref's referent is already available, we can deliver an exact + * answer with considerably less work than running the backref node's NFA. + * + * Return match endpoint for longest or shortest valid repeated match, + * or NULL if there is no valid match. + * + * Should be in sync with cbrdissect(), although that has the different task + * of checking a match to a predetermined section of the string. + */ +static chr * +dfa_backref(struct vars *v, + struct dfa *d, + chr *start, /* where the match should start */ + chr *min, /* match must end at or after here */ + chr *max, /* match must end at or before here */ + bool shortest) +{ + int n = d->backno; + int backmin = d->backmin; + int backmax = d->backmax; + size_t numreps; + size_t minreps; + size_t maxreps; + size_t brlen; + chr *brstring; + chr *p; + + /* get the backreferenced string (caller should have checked this) */ + if (v->pmatch[n].rm_so == -1) + return NULL; + brstring = v->start + v->pmatch[n].rm_so; + brlen = v->pmatch[n].rm_eo - v->pmatch[n].rm_so; + + /* special-case zero-length backreference to avoid divide by zero */ + if (brlen == 0) + { + /* + * matches only a zero-length string, but any number of repetitions + * can be considered to be present + */ + if (min == start && backmin <= backmax) + return start; + return NULL; + } + + /* + * convert min and max into numbers of possible repetitions of the backref + * string, rounding appropriately + */ + if (min <= start) + minreps = 0; + else + minreps = (min - start - 1) / brlen + 1; + maxreps = (max - start) / brlen; + + /* apply bounds, then see if there is any allowed match length */ + if (minreps < backmin) + minreps = backmin; + if (backmax != DUPINF && maxreps > backmax) + maxreps = backmax; + if (maxreps < minreps) + return NULL; + + /* quick exit if zero-repetitions match is valid and preferred */ + if (shortest && minreps == 0) + return start; + + /* okay, compare the actual string contents */ + p = start; + numreps = 0; + while (numreps < maxreps) + { + if ((*v->g->compare) (brstring, p, brlen) != 0) + break; + p += brlen; + numreps++; + if (shortest && numreps >= minreps) + break; + } + + if (numreps >= minreps) + return p; + return NULL; +} + /* * lastcold - determine last point at which no progress had been made */ @@ -563,6 +678,8 @@ newdfa(struct vars *v, d->lastpost = NULL; d->lastnopr = NULL; d->search = d->ssets; + d->backno = -1; /* may be set by caller */ + d->backmin = d->backmax = 0; /* initialization of sset fields is done as needed */ diff --git a/src/backend/regex/regexec.c b/src/backend/regex/regexec.c index cf95989948..8d7777f8c6 100644 --- a/src/backend/regex/regexec.c +++ b/src/backend/regex/regexec.c @@ -77,6 +77,9 @@ struct dfa chr *lastpost; /* location of last cache-flushed success */ chr *lastnopr; /* location of last cache-flushed NOPROGRESS */ struct sset *search; /* replacement-search-pointer memory */ + int backno; /* if DFA for a backref, subno it refers to */ + short backmin; /* min repetitions for backref */ + short backmax; /* max repetitions for backref */ bool ismalloced; /* should this struct dfa be freed? */ bool arraysmalloced; /* should its subsidiary arrays be freed? */ }; @@ -154,6 +157,7 @@ static int creviterdissect(struct vars *, struct subre *, chr *, chr *); static chr *longest(struct vars *, struct dfa *, chr *, chr *, int *); static chr *shortest(struct vars *, struct dfa *, chr *, chr *, chr *, chr **, int *); static int matchuntil(struct vars *, struct dfa *, chr *, struct sset **, chr **); +static chr *dfa_backref(struct vars *, struct dfa *, chr *, chr *, chr *, bool); static chr *lastcold(struct vars *, struct dfa *); static struct dfa *newdfa(struct vars *, struct cnfa *, struct colormap *, struct smalldfa *); static void freedfa(struct dfa *); @@ -342,13 +346,23 @@ static struct dfa * getsubdfa(struct vars *v, struct subre *t) { - if (v->subdfas[t->id] == NULL) + struct dfa *d = v->subdfas[t->id]; + + if (d == NULL) { - v->subdfas[t->id] = newdfa(v, &t->cnfa, &v->g->cmap, DOMALLOC); + d = newdfa(v, &t->cnfa, &v->g->cmap, DOMALLOC); if (ISERR()) return NULL; + /* set up additional info if this is a backref node */ + if (t->op == 'b') + { + d->backno = t->backno; + d->backmin = t->min; + d->backmax = t->max; + } + v->subdfas[t->id] = d; } - return v->subdfas[t->id]; + return d; } /* @@ -369,6 +383,7 @@ getladfa(struct vars *v, v->ladfas[n] = newdfa(v, &sub->cnfa, &v->g->cmap, DOMALLOC); if (ISERR()) return NULL; + /* a LACON can't contain a backref, so nothing else to do */ } return v->ladfas[n]; } @@ -927,6 +942,9 @@ crevcondissect(struct vars *v, /* * cbrdissect - dissect match for backref node + * + * The backref match might already have been verified by dfa_backref(), + * but we don't know that for sure so must check it here. */ static int /* regexec return code */ cbrdissect(struct vars *v,
-- -- PostgreSQL database dump -- -- Dumped from database version 14devel -- Dumped by pg_dump version 14devel SET statement_timeout = 0; SET lock_timeout = 0; SET idle_in_transaction_session_timeout = 0; SET client_encoding = 'UTF8'; SET standard_conforming_strings = on; SELECT pg_catalog.set_config('search_path', '', false); SET check_function_bodies = false; SET xmloption = content; SET client_min_messages = warning; SET row_security = off; SET default_tablespace = ''; SET default_table_access_method = heap; -- -- Name: trouble; Type: TABLE; Schema: public; Owner: postgres -- CREATE TABLE public.trouble ( pattern text, subject text ); ALTER TABLE public.trouble OWNER TO postgres; -- -- Data for Name: trouble; Type: TABLE DATA; Schema: public; Owner: postgres -- COPY public.trouble (pattern, subject) FROM stdin; (["'`])(?:\\\\\\1|.)*?\\1 /* */\nvar macegallery = "{\\"i18n\\":{\\"of\\":\\"of\\"},\\"html\\":\\"\\\\n<div class=\\\\\\"g1-gallery-wrapper g1-gallery-dark\\\\\\">\\\\n\\\\t<div class=\\\\\\"g1-gallery\\\\\\">\\\\n\\\\t\\\\t<div class=\\\\\\"g1-gallery-header\\\\\\">\\\\n\\\\t\\\\t\\\\t<div class=\\\\\\"g1-gallery-header-left\\\\\\">\\\\n\\\\t\\\\t\\\\t\\\\t<div class=\\\\\\"g1-gallery-logo\\\\\\">\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t<\\\\\\/div>\\\\n\\\\t\\\\t\\\\t\\\\t<div class=\\\\\\"g1-gallery-title g1-gamma g1-gamma-1st\\\\\\">{title}<\\\\\\/div>\\\\n\\\\t\\\\t\\\\t<\\\\\\/div>\\\\n\\\\t\\\\t\\\\t<div class=\\\\\\"g1-gallery-header-right\\\\\\">\\\\n\\\\t\\\\t\\\\t\\\\t<div class=\\\\\\"g1-gallery-back-to-slideshow\\\\\\">Back to slideshow<\\\\\\/div>\\\\n\\\\t\\\\t\\\\t\\\\t<div class=\\\\\\"g1-gallery-thumbs-button\\\\\\"><\\\\\\/div>\\\\n\\\\t\\\\t\\\\t\\\\t<div class=\\\\\\"g1-gallery-numerator\\\\\\">{numerator}<\\\\\\/div>\\\\n\\\\t\\\\t\\\\t\\\\t<div class=\\\\\\"g1-gallery-close-button\\\\\\"><\\\\\\/div>\\\\n\\\\t\\\\t\\\\t<\\\\\\/div>\\\\n\\\\t\\\\t<\\\\\\/div>\\\\n\\\\t\\\\t<div class=\\\\\\"g1-gallery-body\\\\\\">\\\\n\\\\t\\\\t\\\\t<div class=\\\\\\"g1-gallery-frames\\\\\\">\\\\n\\\\t\\\\t\\\\t\\\\t{frames}\\\\n\\\\t\\\\t\\\\t<\\\\\\/div>\\\\n\\\\t\\\\t\\\\t<div class=\\\\\\"g1-gallery-thumbnails32\\\\\\">\\\\n\\\\t\\\\t\\\\t\\\\t<div class=\\\\\\"g1-gallery-thumbnails-collection\\\\\\">\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t{thumbnails32}\\\\n\\\\t\\\\t\\\\t\\\\t<\\\\\\/div>\\\\n\\\\t\\\\t\\\\t<\\\\\\/div>\\\\n\\\\t\\\\t\\\\t<div class=\\\\\\"g1-gallery-sidebar\\\\\\">\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t<div class=\\\\\\"g1-gallery-shares\\\\\\">\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t<\\\\\\/div>\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t<div class=\\\\\\"g1-gallery-ad\\\\\\"><\\\\\\/div>\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t<div class=\\\\\\"g1-gallery-thumbnails\\\\\\">\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t<div class=\\\\\\"g1-gallery-thumbnails-up\\\\\\"><\\\\\\/div>\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t<div class=\\\\\\"g1-gallery-thumbnails-collection\\\\\\">{thumbnails}<\\\\\\/div>\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t<div class=\\\\\\"g1-gallery-thumbnails-down\\\\\\"><\\\\\\/div>\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t<\\\\\\/div>\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t<\\\\\\/div>\\\\n\\\\t\\\\t<\\\\\\/div>\\\\n\\\\t<\\\\\\/div>\\\\n<\\\\\\/div>\\\\n\\",\\"shares\\":\\"<script type=\\\\\\"text\\\\\\/javascript\\\\\\">\\\\n\\\\t\\\\t\\\\t(function () {\\\\n\\\\t\\\\t\\\\t\\\\tvar triggerOnLoad = false;\\\\n\\\\n\\\\t\\\\t\\\\t\\\\twindow.apiShareOnFB = function() {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tjQuery('body').trigger('snaxFbNotLoaded');\\\\n\\\\t\\\\t\\\\t\\\\t\\\\ttriggerOnLoad = true;\\\\n\\\\t\\\\t\\\\t\\\\t};\\\\n\\\\n\\\\t\\\\t\\\\t\\\\tvar _fbAsyncInitGallery = window.fbAsyncInitGallery;\\\\n\\\\n\\\\t\\\\t\\\\t\\\\twindow.fbAsyncInitGallery = function() {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tFB.init({\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tappId : '',\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\txfbml : true,\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tversion : 'v3.0'\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t});\\\\n\\\\t\\\\t\\\\t\\\\t\\\\twindow.apiShareOnFB_mace_replace_unique = function() {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tvar shareTitle \\\\t\\\\t = \\\\\\"mace_replace_noesc_title\\\\\\"; \\\\\\/\\\\\\/ Double quotes to allow ' chars.\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tvar shareDescription\\\\t= '';\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tvar shareImage\\\\t = 'mace_replace_noesc_image_url';\\\\n\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tFB.login(function(response) {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tif (response.status === 'connected') {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tvar objectToShare = {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t'og:url': 'mace_replace_noesc_shortlink', \\\\\\/\\\\\\/ Url to share.\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t'og:title': shareTitle,\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t'og:description': shareDescription\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t};\\\\n\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\\\/\\\\\\/ Add image only if set. FB fails otherwise.\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tif (shareImage) {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tobjectToShare['og:image'] = shareImage;\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t}\\\\n\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tFB.ui({\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tmethod: 'share_open_graph',\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\taction_type: 'og.shares',\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\taction_properties: JSON.stringify({\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tobject : objectToShare\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t})\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t},\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\\\/\\\\\\/ callback\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tfunction(response) {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t});\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t});\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t};\\\\n\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\\\/\\\\\\/ Fire original callback.\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tif (typeof _fbAsyncInitGallery === 'function') {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t_fbAsyncInitGallery();\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t}\\\\n\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\\\/\\\\\\/ Open share popup as soon as possible, after loading FB SDK.`\\\\n\\\\t\\\\t\\\\t\\\\t\\\\tif (triggerOnLoad) {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tsetTimeout(function() {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t\\\\tapiShareOnFB();\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t\\\\t}, 1000);\\\\n\\\\t\\\\t\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t\\\\t\\\\t};\\\\n\\\\n\\\\t\\\\t\\\\t\\\\t\\\\\\/\\\\\\/ JS SDK loaded before we hook into it. Trigger callback now.\\\\n\\\\t\\\\t\\\\t\\\\tif (typeof window.FB !== 'undefined') {\\\\n\\\\t\\\\t\\\\t\\\\t\\\\twindow.fbAsyncInitGallery();\\\\n\\\\t\\\\t\\\\t\\\\t}\\\\n\\\\t\\\\t\\\\t\\\\tjQuery('body').on('maceGalleryItemChanged', function(){\\\\n\\\\t\\\\t\\\\t\\\\t\\\\twindow.fbAsyncInitGallery();\\\\n\\\\t\\\\t\\\\t\\\\t});\\\\n\\\\t\\\\t\\\\t})();\\\\n\\\\t\\\\t<\\\\\\/script>\\\\n\\\\n<a class=\\\\\\"g1-gallery-share g1-gallery-share-fb\\\\\\" href=\\\\\\"#\\\\\\" title=\\\\\\"Share on Facebook\\\\\\" onclick=\\\\\\"apiShareOnFB_mace_replace_unique(); return false;\\\\\\" target=\\\\\\"_blank\\\\\\" rel=\\\\\\"nofollow\\\\\\">Share on Facebook<\\\\\\/a><a class=\\\\\\"g1-gallery-share g1-gallery-share-twitter\\\\\\" href=\\\\\\"https:\\\\\\/\\\\\\/twitter.com\\\\\\/intent\\\\\\/tweet?text=mace_replace_title&url=mace_replace_shortlink\\\\\\" title=\\\\\\"Share on Twitter\\\\\\" target=\\\\\\"_blank\\\\\\" rel=\\\\\\"nofollow\\\\\\">Share on Twitter<\\\\\\/a><a class=\\\\\\"g1-gallery-share g1-gallery-share-pinterest\\\\\\" href=\\\\\\"https:\\\\\\/\\\\\\/pinterest.com\\\\\\/pin\\\\\\/create\\\\\\/button\\\\\\/?url=mace_replace_shortlink&description=mace_replace_title&media=mace_replace_image_url\\\\\\" title=\\\\\\"Share on Pinterest\\\\\\" target=\\\\\\"_blank\\\\\\" rel=\\\\\\"nofollow\\\\\\">Share on Pinterest<\\\\\\/a>\\"}";\n/* */ \. -- -- PostgreSQL database dump complete --