Sphinx::Search(3pm)
π Sphinx::Search β the Perl bestie for the Sphinx search engine π
okay so, Sphinx::Search? it's the Perl API client for the Sphinx open-source SQL full-text indexing search engine. it lives in the User Contributed Perl Documentation section (that's the 3pm in Sphinx::Search(3pm), iykyk). come sit with me, we're gonna get through this together π«Ά
β¨ VERSION β¨
first things first bestie, and I need you to hear me on this one:
you *MUST* install a version which is compatible with your version of Sphinx. like, non-negotiable. the wrong pairing is giving chaos π
here's the whole compatibility roster, no skips:
- Use version
0.31forSphinx-2.2.8-releaseor later (or useDBIinstead) - Use version
0.28forSphinx-2.0.8-releaseor later - Use version
0.27.2forSphinx-2.0.3-release(svn-r3043) - Use version
0.26.1forSphinx-2.0.1-beta(svn-r2792) - Use version
0.25_03for Sphinxsvn-r2575 - Use version
0.24.1forSphinx-1.10-beta(svn-r2420) - Use version
0.23_02for Sphinxsvn-r2269(experimental) - Use version
0.22for Sphinx0.9.9-rc2and later (Please read the Compatibility Note underSetEncodersregarding encoding changes) - Use version
0.15for Sphinx0.9.9-svn-r1674 - Use version
0.12for Sphinx0.9.8 - Use version
0.11for Sphinx0.9.8-rc1 - Use version
0.10for Sphinx0.9.8-svn-r1112 - Use version
0.09for Sphinx0.9.8-svn-r985 - Use version
0.08for Sphinx0.9.8-svn-r871 - Use version
0.06for Sphinx0.9.8-svn-r820 - Use version
0.05for Sphinx0.9.8-cvs-20070907 - Use version
0.02for Sphinx0.9.8-cvs-20070818
πΈ SYNOPSIS πΈ
the vibe in code form, periodt:
use Sphinx::Search;
$sph = Sphinx::Search->new();
# Standard API query
$results = $sph->SetSortMode(SPH_SORT_RELEVANCE)
->Query("search terms");
# SphinxQL query
$results = $sph->SphinxQL("SELECT * FROM myindex WHERE MATCH('search terms')");
π¦ DESCRIPTION π¦
so this is the Perl API client for the Sphinx open-source SQL full-text indexing search engine, <http://www.sphinxsearch.com>. iconic. π
now, tea time: since 0.9.9, Sphinx supports a native MySQL-protocol client, aka DBI with DBD::mysql. meaning you can configure the server to have a mysql41 listening port and then literally just do:
my $dbh = DBI->connect('dbi:mysql:host=127.0.0.1;port=9306;mysql_enable_utf8=1') or die "Failed to connect via DBI";
my $sth = $dbh->prepare_cached("SELECT * FROM myindex WHERE MATCH('search terms')");
$sth->execute();
while (my $row = $sth->fetchrow_arrayref) {
... # Collect results
}
and here's the part that's gonna sting a little: the DBI client turns out to be significantly (about 5x) faster than this pure-Perl API. π you should probably be using that instead. I'm not being shady, I'm being honest, that's what the page says.
this module also supports SphinxQL queries, with the small advantage that you can use either the standard API or the SphinxQL API over the one port (i.e. the searchd server does not need to be configured with a mysql41 listening port). cute, right?
and given that the DBI client has several advantages over this API, future updates of this module are unlikely. so... yeah. it's giving legacy, but respectfully. π«Ά
π CONSTRUCTOR π
new
$sph = Sphinx::Search->new;
$sph = Sphinx::Search->new(\%options);
Create a new Sphinx::Search instance. your main character moment starts here β¨
OPTIONS
logβ Specify an optional logger instance. This can be any class that provideserror,warn,info, anddebugmethods (e.g. seeLog::Log4perl). Logging is disabled if no logger instance is provided.debugβ Debug flag. If set (and a logger instance is specified), debugging messages will be generated.
π METHODS π
GetLastError
$error = $sph->GetLastError;
Get last error message (string). the receipts, basically. π
GetLastWarning
$warning = $sph->GetLastWarning;
Get last warning message (string). softer receipts. π
IsConnectError
Check connection error flag (to differentiate between network connection errors and bad responses). Returns true value on connection error.
SetEncoders
$sph->SetEncoders(\&encode_function, \&decode_function)
COMPATIBILITY NOTE:SetEncoders()was introduced in version0.17. Prior to that, all strings were considered to be sequences of bytes which may have led to issues with multi-byte characters. If you were previously encoding/decoding strings external toSphinx::Search, you will need to disable encoding/decoding by settingSphinx::Searchto use raw values as explained below (or modify your code and letSphinx::Searchdo the recoding).
Set the string encoder/decoder functions for transferring strings between perl and Sphinx. The encoder should take the perl internal representation and convert to the bytestream that searchd expects, and the decoder should take the bytestream returned by searchd and convert to perl format.
The searchd format will depend on the 'charset_type' index setting in the Sphinx configuration file.
The coders default to encode_utf8 and decode_utf8 respectively, which are compatible with the 'utf8' charset_type.
If either the encoder or decoder functions are left undefined in the call to SetEncoders, they return to their default values.
If you wish to send raw values (no encoding/decoding), supply a function that simply returns its argument, e.g.
$sph->SetEncoders( sub { shift }, sub { shift });
Returns $sph.
SetServer
$sph->SetServer($host, $port);
$sph->SetServer($path, $port);
In the first form, sets the host (string) and port (integer) details for the searchd server using a network (INET) socket (default is localhost:9312).
In the second form, where $path is a local filesystem path (optionally prefixed by 'unix://'), sets the client to access the searchd server via a local (UNIX domain) socket at the specified path.
Returns $sph.
SetConnectTimeout
$sph->SetConnectTimeout($timeout)
Set server connection timeout (in seconds).
Returns $sph.
SetConnectRetries
$sph->SetConnectRetries($retries)
Set server connection retries (in case of connection fail).
Returns $sph.
SetLimits
$sph->SetLimits($offset, $limit);
$sph->SetLimits($offset, $limit, $max);
Set match offset/limits, and optionally the max number of matches to return.
Returns $sph.
SetMaxQueryTime
$sph->SetMaxQueryTime($millisec);
Set maximum query time, in milliseconds, per index.
The value may not be negative; 0 means "do not limit".
Returns $sph.
SetMatchMode
** DEPRECATED ** β οΈ like, it's over bestie, move on
$sph->SetMatchMode($mode);
Set match mode, which may be one of:
SPH_MATCH_ALLβ Match all wordsSPH_MATCH_ANYβ Match any wordsSPH_MATCH_PHRASEβ Exact phrase matchSPH_MATCH_BOOLEANβ Boolean match, usingAND(&),OR(|),NOT(!,-) and parenthetic grouping.SPH_MATCH_EXTENDEDβ Extended match, which includes the Boolean syntax plus field, phrase and proximity operators.
Returns $sph.
SetRankingMode
$sph->SetRankingMode(SPH_RANK_BM25, $rank_exp);
Set ranking mode, which may be one of:
SPH_RANK_PROXIMITY_BM25β Default mode, phrase proximity major factor and BM25 minor oneSPH_RANK_BM25β Statistical mode, BM25 ranking only (faster but worse quality)SPH_RANK_NONEβ No ranking, all matches get a weight of1SPH_RANK_WORDCOUNTβ Simple word-count weighting, rank is a weighted sum of per-field keyword occurence countsSPH_RANK_MATCHANYβ Returns rank as it was computed inSPH_MATCH_ANYmode earlier, and is internally used to emulateSPH_MATCH_ANYqueries.SPH_RANK_FIELDMASKβ Returns a 32-bit mask with N-th bit corresponding to N-th fulltext field, numbering from 0. The bit will only be set when the respective field has any keyword occurences satisfiying the query.SPH_RANK_SPH04βSPH_RANK_SPH04is generally based on the defaultSPH_RANK_PROXIMITY_BM25ranker, but additionally boosts the matches when they occur in the very beginning or the very end of a text field.SPH_RANK_EXPRβ Allows the ranking formula to be specified at run time. It exposes a number of internal text factors and lets you define how the final weight should be computed from those factors.$rank_expshould be set to the ranking expression string, e.g. to emulateSPH_RANK_PROXIMITY_BM25, use"sum(lcs*user_weight)*1000+bm25".
Returns $sph.
SetSortMode
$sph->SetSortMode(SPH_SORT_RELEVANCE);
$sph->SetSortMode($mode, $sortby);
Set sort mode, which may be any of:
SPH_SORT_RELEVANCEβ sort by relevanceSPH_SORT_ATTR_DESC,SPH_SORT_ASCβ Sort by attribute descending/ascending.$sortbyspecifies the sorting attribute.SPH_SORT_TIME_SEGMENTSβ Sort by time segments (last hour/day/week/month) in descending order, and then by relevance in descending order.$sortbyspecifies the time attribute.SPH_SORT_EXTENDEDβ Sort by SQL-like syntax.$sortbyis the sorting specification.SPH_SORT_EXPR
Returns $sph.
SetWeights
** DEPRECATED ** β οΈ
$sph->SetWeights([ 1, 2, 3, 4]);
This method is deprecated. Use SetFieldWeights instead.
Set per-field (integer) weights. The ordering of the weights correspond to the ordering of fields as indexed.
Returns $sph.
SetFieldWeights
$sph->SetFieldWeights(\%weights);
Set per-field (integer) weights by field name. The weights hash provides field name to weight mappings.
Takes precedence over SetWeights.
Unknown names will be silently ignored. Missing fields will be given a weight of 1.
Returns $sph.
SetIndexWeights
$sph->SetIndexWeights(\%weights);
Set per-index (integer) weights. The weights hash is a mapping of index name to integer weight.
Returns $sph.
SetIDRange
$sph->SetIDRange($min, $max);
Set IDs range only match those records where document ID is between $min and $max (including $min and $max)
Returns $sph.
SetFilter
$sph->SetFilter($attr, \@values);
$sph->SetFilter($attr, \@values, $exclude);
Sets the results to be filtered on the given attribute. Only results which have attributes matching the given values will be returned. (Attribute values must be integers).
This may be called multiple times with different attributes to select on multiple attributes.
If 'exclude' is set, excludes results that match the filter.
Returns $sph.
SetFilterString
$sph->SetFilterString($attr, $value)
$sph->SetFilterString($attr, $value, $exclude)
Adds new string value filter. Only those documents where $attr column value matches the string value from $value will be matched (or rejected, if $exclude is true).
SetFilterRange
$sph->SetFilterRange($attr, $min, $max);
$sph->SetFilterRange($attr, $min, $max, $exclude);
Sets the results to be filtered on a range of values for the given attribute. Only those records where $attr column value is between $min and $max (including $min and $max) will be returned.
If 'exclude' is set, excludes results that fall within the given range.
Returns $sph.
SetFilterFloatRange
$sph->SetFilterFloatRange($attr, $min, $max, $exclude);
Same as SetFilterRange, except it's not scared of decimals. Floating point values? Absolutely allowed, bestie. π
Returns $sph.
SetGeoAnchor
$sph->SetGeoAnchor($attrlat, $attrlong, $lat, $long);
Okay so this one sets up an anchor point for doing geosphere distance math in your filters and your sorting. Distances get computed relative to this point, like dropping a pin π and measuring everything from there.
$attrlatβ the name of the latitude attribute$attrlongβ the name of the longitude attribute$latβ anchor point latitude, in radians$longβ anchor point longitude, in radians
Returns $sph.
SetGroupBy
$sph->SetGroupBy($attr, $func);
$sph->SetGroupBy($attr, $func, $groupsort);
Sets the attribute and the function for grouping your results. This is the "put my matches into little buckets" button πͺ£β¨
When you're in grouping mode, every match gets assigned to a group based on the grouping function's value. Each group keeps track of the total match count, plus the best match in that group according to whatever your current sorting function is. The final result set has one best match per group, and each one comes with its grouping function value and match count attached.
$attris any valid attribute. Wanna turn grouping back off? UseResetGroupBy.$funcis one of these icons:SPH_GROUPBY_DAYβ groups by day (assumes a timestamp type attribute shaped likeYYYYMMDD)SPH_GROUPBY_WEEKβ groups by week (assumes a timestamp type attribute shaped likeYYYYNNN)SPH_GROUPBY_MONTHβ groups by month (assumes a timestamp type attribute shaped likeYYYYMM)SPH_GROUPBY_YEARβ groups by year (assumes a timestamp type attribute shaped likeYYYY)SPH_GROUPBY_ATTRβ groups by the attribute's valueSPH_GROUPBY_ATTRPAIRβ groups by two attributes: the one you gave, plus the attribute that comes immediately after it in the sequence of indexed attributes. So heads up, the attribute you specify may not be the last of the indexed attributes π
Your groups in the results can be sorted by any SQL-like sorting clause, and that includes both document attributes and these special internal Sphinx attributes:
@id - document ID;
@weight, @rank, @relevance - match weight;
@group - group by function value;
@count - number of matches in group.
The default mode sorts by the groupby value in descending order, aka "@group desc".
In the results set, "total_found" holds the total amount of matching groups across the whole index.
β οΈ WARNING: grouping happens in fixed memory, so its results are only approximate. That means there might be more groups reported intotal_foundthan actually exist, and@countmight be underestimated too. Not a bug, just the vibe. π
Example time, because this one's actually so helpful: if you sort by relevance and group by a "published" attribute with the SPH_GROUPBY_DAY function, then your result set will hold only the most relevant match for each day where anything got published, with the day number and per-day match count attached β and it'll be sorted by day number descending, aka recent days first ποΈβ¨
SetGroupDistinct
$sph->SetGroupDistinct($attr);
Sets the count-distinct attribute for group-by queries. One distinct little attribute, doing its thing.
SetRetries
$sph->SetRetries($count, $delay);
Sets the distributed retries count and delay. Because sometimes you just gotta try again, bestie π
SetOverride
DEPRECATED π¨ (yes, in all caps, the original is yelling it too)
$sph->SetOverride($attrname, $attrtype, $values);
Sets attribute value overrides. Heads up: there can be only one override per attribute. And $values has to be a hash that maps document IDs to attribute values.
SetSelect
$sph->SetSelect($select)
Sets the select list β attributes or expressions. SQL-like syntax, you know the drill πββοΈ
SetQueryFlag
$sph->SetQueryFlag($flag_name, $flag_value);
SetOuterSelect
$sph->SetOuterSelect($orderby, $offset, $limit)
ResetFilters
$sph->ResetFilters;
Clears all the filters. Fresh start, no cap π§Όβ¨
ResetGroupBy
$sph->ResetGroupBy;
Clears all group-by settings (for multi-queries).
ResetOverrides
Clears all attribute value overrides (for multi-queries).
ResetQueryFlag
Clears all query flags.
ResetOuterSelect
Clears all outer select settings.
Query
$results = $sph->Query($query, $index);
Connects to the searchd server and runs your search query. The moment of truth β¨
- query is the query string
- index is the index name to query. Default is
"*", which means query all the indexes. Wanna search multiple indexes? Use a space or comma separated list.
Returns undef on failure.
Returns a hash on success, and that hash has these keys:
matchesβ array containing hashes with the found documents:"doc","weight","group","stamp"totalβ total amount of matches retrieved (up toSPH_MAX_MATCHES, seesphinx.h)total_foundβ total amount of matching documents in the indextimeβ search timewordsβ hash which maps query terms (stemmed!) to a"docs","hits"hash
Returns the results array on success, undef on error.
AddQuery
$sph->AddQuery($query, $index);
Adds a query to a batch request. Batching is lowkey iconic: it lets searchd do internal optimizations when it can, and it cuts down network connection overhead in literally every case.
For instance β running the exact same query with different groupby settings means searchd can do the expensive full-text search and ranking operation only once, then compute multiple groupby results from that one output. Efficiency? Ate. π
Parameters are exactly the same as in the Query() call.
Returns the corresponding index into the results array that RunQueries() gives back.
RunQueries
$sph->RunQueries
Runs the batch of queries you built up with AddQuery.
Returns undef on network IO failure.
Returns an array of result sets on success.
Each result set in that returned array is a hash containing the same keys as the hash returned by Query, plus:
errorβ errors, if any, for this query.warningβ any warnings associated with the query.
SphinxQL
my $results = $sph->SphinxQL($sphinxql_query);
This is an alternative implementation of the SphinxQL API compared to the DBI option. Ngl, it was kind of an experiment, and the DBI driver turned out to have way better performance. It might still be useful to some people, but honestly if you're thinking about using this method, you should probably just connect directly via DBI instead. π€·ββοΈ
Results come back in a hash containing an array of 'columns' and 'rows', and possibly a warning count. If a server-side error happens, the hash contains the 'error' field. If a communication error happens, the return value is undef. In either error case, GetLastError will return the error.
BuildExcerpts
$excerpts = $sph->BuildExcerpts($docs, $index, $words, $opts)
Generates document excerpts for the documents you specify. Little highlight reels for your text π¬β¨
- docs β an array reference of strings that represent the document contents
- index β a string specifying the index whose settings will be used for stemming, lexing and case folding
- words β a string containing the words to highlight
- opts β a hash containing extra optional highlighting parameters:
before_matchβ a string to insert before a set of matching words, default is"<b>"after_matchβ a string to insert after a set of matching words, default is"<b>"chunk_separatorβ a string to insert between excerpt chunks, default is" ... "limitβ max excerpt size in symbols (codepoints), default is256limit_passagesβ limits the maximum number of passages that can be included in the snippet. Integer, default is0(no limit).limit_wordsβ limits the maximum number of keywords that can be included in the snippet. Integer, default is0(no limit).aroundβ how many words to highlight around each match, default is5exact_phraseβ whether to highlight exact phrase matches only, default isfalsesingle_passageβ whether to extract only the single best passage, default isfalseuse_boundariesweight_orderβ whether to sort the extracted passages in order of relevance (decreasing weight), or in order of appearance in the document (increasing position). Boolean, default isfalse.query_modeβ whether to handle$wordsas a query in extended syntax, or as a bag of words (the default behavior). For instance, in query mode("one two" | "three four")will only highlight and include those occurrences"one two"or"three four"when the two words from each pair are adjacent to each other. In default mode, any single occurrence of"one","two","three", or"four"would get highlighted. Boolean, default isfalse.force_all_wordsβ ignores the snippet length limit until it includes all the keywords. Boolean, default isfalse.start_passage_idβ specifies the starting value of the%PASSAGE_ID%macro (which gets detected and expanded in thebefore_match,after_matchstrings). Integer, default is1.load_filesβ whether to handle$docsas data to extract snippets from (the default behavior), or to treat it as file names and load the data from the specified files on the server side. Boolean, default isfalse.html_strip_modeβ HTML stripping mode setting. Defaults to"index", which means the index settings get used. The other values are"none"and"strip", which forcibly skip or apply stripping irregardless of index settings; and"retain", which retains HTML markup and protects it from highlighting. The"retain"mode can only be used when highlighting full documents, and so it requires that no snippet size limits are set. String, allowed values are"none","strip","index", and"retain".allow_emptyβ allows an empty string to be returned as the highlighting result when a snippet couldn't be generated (no keywords match, or no passages fit the limit). By default, the beginning of the original text would be returned instead of an empty string. Boolean, default isfalse.passage_boundaryemit_zonesload_files_scattered
Returns undef on failure.
Returns an array ref of string excerpts on success.
BuildKeywords
$results = $sph->BuildKeywords($query, $index, $hits)
Generates a keyword list for a given query. Returns undef on failure. On success it returns an array of hashes, where each hash describes a word in the query, with these keys:
tokenizedβ tokenised term from the querynormalizedβ normalised term from the querydocsβ number of docs in which the word was found (if$hitsis true)hitsβ number of occurrences of the word (if$hitsis true)
EscapeString
$escaped = $sph->EscapeString('abcde!@#$%')
Inserts a backslash before all non-word characters in the given string. Escaping is self care π«Ά
UpdateAttributes
$sph->UpdateAttributes($index, \@attrs, \%values);
$sph->UpdateAttributes($index, \@attrs, \%values, $mva);
$sph->UpdateAttributes($index, \@attrs, \%values, $mva, $ignorenonexistent);
Updates the specified attributes on the specified documents.
indexβ the name of the index</p> you're updating π like, which one are we touching today bestieattrsβ an array of attribute name strings π just a list of the attribute names, that's itvaluesβ a hash where the key is the document id and the value is an array of the new attribute values β¨ so like,id => [new, new, new]mvaβ if you set this, it means there's an update of MVA attributes π (multi-value things, iykyk)ignorenonexistentβ if you set this, the update will silently ignore any warnings about trying to update a column that doesn't exist in the current index schema π«Ά it just... doesn't say anything. very chill of it.
Returns the number of actually updated documents (0 or more) on success π
Returns undef on failure π
Usage example, straight up:
$sph->UpdateAttributes("test1", [ qw/group_id/ ], { 1 => [ 456] }) );
(that's the example exactly as written, extra closing paren and all β I'm not touching it, no cap)
Open
$sph->Open()
Opens a persistent connection for all the queries you're about to run πΈ
The whole point: making Sphinx queries has network connection overhead, and that's annoying. So you can call $sph->Open(), then run however many queries you want, and then call $sph->Close() when you're done β¨ basically you're keeping the line open so you're not redialing every single time. main character efficiency.
Returns 1 on success, 0 on failure π
Close
$sph->Close()
Closes a persistent connection. Bye bestie, the line is closed π¦
Returns 1 on success, 0 on failure π
Status
$status = $sph->Status()
$status = $sph->Status($session)
Queries searchd status and hands you back a hash of status variable name and value pairs π so you get the whole vibe report, name => value, name => value.
Returns undef on failure π
FlushAttributes
Okay so real talk β the original doc gives us literally nothing here π«₯ just the name, and then it moves straight on to SEE ALSO. No description, no arguments, no return values, nothing. I'm not gonna make anything up about it because I refuse to be the bestie who lies to you π
π SEE ALSO
π NOTES
There is (or was) a bundled Sphinx.pm in the contrib area of the Sphinx source distribution, and that's what got used as the starting point of Sphinx::Search π± Maintenance of that version appears to have lapsed at sphinx-0.9.7, so a whole bunch of the newer API calls are just not available over there π
Sphinx::Search is mostly compatible with the old Sphinx.pm, except:
- On failure,
Sphinx::Searchreturnsundefrather than0or-1. Yeah. That's the plot twist. π Sphinx::Search'Set' functions are cascadable, which means you can do stuff like this:
Sphinx::Search->new ->SetMatchMode(SPH_MATCH_ALL)
->SetSortMode(SPH_SORT_RELEVANCE) ->Query("search terms")
Sphinx::Searchalso provides documentation and unit tests, which were the main motivations for branching from the earlier work β¨ (documentation and tests, the two love languages honestly)
π AUTHOR
Jon Schutz
π BUGS
Please report any bugs or feature requests to bug-sphinx-search at rt.cpan.org, or through the web interface at <http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Sphinx-Search> π
I will be notified, and then you'll automatically be notified of progress on your bug as I make changes π«Ά so like, you don't have to keep checking, the updates come to you. iconic behaviour honestly.
π« SUPPORT
You can find documentation for this module with the perldoc command π
perldoc Sphinx::Search
You can also look for information at:
- AnnoCPAN: Annotated CPAN documentation πΈ
<http://annocpan.org/dist/Sphinx-Search>
- CPAN Ratings β
<http://cpanratings.perl.org/d/Sphinx-Search>
- RT: CPAN's request tracker π
<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Sphinx-Search>
- Search CPAN π¦
<http://search.cpan.org/dist/Sphinx-Search>
π ACKNOWLEDGEMENTS
This module is based on Sphinx.pm (not deployed to CPAN) for Sphinx version 0.9.7-rc1, by Len Kranendonk, which was in turn based on the Sphinx PHP API π±
Thanks to Alexey Kholodkov for contributing a significant patch for handling persistent connections π«Ά absolute legend move.
π COPYRIGHT & LICENSE
Copyright 2015 Jon Schutz, all rights reserved.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License π
okay that's the whole thing bestie, we made it to the end π₯Ή this page was generated for perl v5.34.0, dated 2022-06-17, and it's the Sphinx::Search(3pm) manual page β go forth and search, you absolute icon π
β¨πΈ
π¦ where this page comes from
- debian bookworm: libsphinx-search-perl 1:0.31-2 Β· GPL-1.0-or-later
- debian trixie: libsphinx-search-perl 1:0.31-2 Β· GPL-1.0-or-later
- ubuntu noble: libsphinx-search-perl 1:0.31-2 Β· GPL-1.0-or-later
- ubuntu resolute: libsphinx-search-perl 1:0.31-2 Β· GPL-1.0-or-later
- ubuntu stonking: libsphinx-search-perl 1:0.31-2 Β· GPL-1.0-or-later
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms