Skip to content

Search

mash.search · Platform

mash.search finds files two different ways, and the difference matters. execute asks the search index, which understands words, phrases and file contents, and may lag a little behind recent changes. byField asks the database for files whose custom field has a value. It’s exact and sees a value the instant it’s written. Pick execute to find by what a file says. Pick byField to find by what a file has been marked with.

execute(where, query) returns a result set. where is a share (name or id) or a dictionary of options such as {share: "Finance", contents: true, rpp: 20, page: 2}. query uses the same search syntax as the search box. The set has total (all matches), page, per_page, last_page, and holds up to one page of results. Loop over it, or ask count() for how many are on this page.

A result is a summary, not a file. It has name, path, size, is_dir, mimetype, modified, share, and highlight (a snippet around the match). Open the real thing with mash.files.get(hit.share, hit.path). Ask for contents: true in the options if you want the text of each hit, which is slower.

Search answers as someone. A script run by a user searches as that user, so results are limited to what they may see. An administrator’s script searches unrestricted. Pass a user as the third argument to search as someone else (a script run by a user can’t).

byField(share, field, value) returns a result set of rows. Each row has path, value and updated_at. A string value matches exactly, a list matches any of them, and no value means “has any value”. It returns up to 200 rows by default (the rpp option, at most 1000), ordered by path. lastPath is the cursor for the next batch, passed back as the after option.

escape makes text safe in a query. Put a name like Smith (Ltd) in a query and the brackets mean something to the search. mash.search.escape("Smith (Ltd)") backslashes them first.

Find invoices that mention a customer, and open each:

set hits to mash.search.execute("Finance", "invoice AND \"Acme Ltd\"")
printline "{{hits.total}} matches"
each hits as hit
printline "{{hit.name}}: {{hit.highlight}}"
end

Search for text a person typed, without letting it change the query:

set typed to "Smith (Ltd) & Co"
printline mash.search.escape(typed)
Smith \(Ltd\) \& Co

Work through everything waiting for review, whatever state the index is in:

set batch to mash.search.byField("Orders", "Status", "Pending")
each batch as row
set doc to mash.files.get("Orders", row.path)
doc.fields.set("Status", "Done")
end

Walk a large share page by page:

set options to {rpp: 500}
while true
set batch to mash.search.byField("Orders", "Customer", null, options)
if count(batch) is 0
break
end
each batch as row
printline row.path
end
set options["after"] to batch.lastPath
end
  • Test for no results with count(), never if batch. A result set is always “true”.
  • The index can be behind. A file uploaded or a field set a second ago may not be in execute yet. Use byField for state you’ve just written.
  • byField only sees files that have a value. “Files with this field empty” can’t be asked. Give each file a starting value, then look for it.
  • The state flip is your cursor. In a loop that sets Pending to Done, the next call no longer finds the finished files, so you don’t need after. Use after for a set that doesn’t change.
  • Search results aren’t File objects. hit.fields doesn’t exist. Open the file first.

Custom fields, mash.fields for searching across shares, and the File type.

search.byField(share: Share, field: mixed, value?: mixed, options?: array = []) → FieldResultSet

Enumerate files in a share by custom-field value — straight from the database, no Solr. Exact and read-your-writes: a value written by fields.set (or the web UI, or Captur) is visible here immediately, making this the right primitive for workflow state machines where search.execute() is eventually consistent.

Value matching: string = equality, array = IN, null/omitted = “field has any value”. Only files WITH a value are enumerable — rows exist only for set values, so “files missing the field” is unanswerable here by design. Prefer positive state machines (set status=pending at discovery, enumerate pending, flip to done after processing).

Each row’s path is share-relative, as mash.files.get(share, path) takes it. Its value is the field’s value, or for a multivalue field the list of every value it holds on that file.

Options: rpp batch size (default 200, capped at 1000) after keyset cursor — return paths > after (use result.lastPath) withValues include each row’s value (default true) user user (or guid) to enforce field read permission for; omitted = system context, unrestricted (mirrors fields[…])

Iteration idioms (test emptiness with count()):

Drain pattern: the state flip IS the cursor while true batch = mash.search.byField(share, “status”, “pending”) if count(batch) == 0 break end each batch as r # … flip to done … end end

Section titled “Drain pattern: the state flip IS the cursor while true batch = mash.search.byField(share, “status”, “pending”) if count(batch) == 0 break end each batch as r # … flip to done … end end”

Static set: keyset pagination opts = {“rpp”: 1000} opts[“after”] = batch.lastPath next = mash.search.byField(share, “f”, null, opts)

Section titled “Static set: keyset pagination opts = {“rpp”: 1000} opts[“after”] = batch.lastPath next = mash.search.byField(share, “f”, null, opts)”
search.escape(str: string) → string

Escape special Lucene/Solr characters in a query string.

search.execute(config: mixed, query: string, user?: User) → SearchResultSet

Execute a search query.