Added basic note searching for SQLite. Should really be using SQLite's full-text search instead.
In sql_load_notes, no longer adding offset clause if start = 0 since SQLite doesn't like offset without limit. Switched from using "ilike" to "like" with lower(), since "ilike" is a PostgreSQL-specific extension. Moved BACKEND flags from Database to Persistent to prevent need from circular imports (model files importing controller.Database).
This commit is contained in:
parent
73219b74ce
commit
410f6f8cbf
|
@ -103,13 +103,20 @@ class Notebook( Persistent ):
|
||||||
def sql_load_notes( self, start = 0, count = None ):
|
def sql_load_notes( self, start = 0, count = None ):
|
||||||
"""
|
"""
|
||||||
Return a SQL string to load a list of all the notes within this notebook.
|
Return a SQL string to load a list of all the notes within this notebook.
|
||||||
|
Note: If the database backend is SQLite, a start parameter cannot be given without also
|
||||||
|
providing a count parameter.
|
||||||
"""
|
"""
|
||||||
if count is not None:
|
if count is not None:
|
||||||
limit_clause = " limit %s" % count
|
limit_clause = " limit %s" % count
|
||||||
else:
|
else:
|
||||||
limit_clause = ""
|
limit_clause = ""
|
||||||
|
|
||||||
return "select id, revision, title, contents, notebook_id, startup, deleted_from_id, rank, user_id from note_current where notebook_id = %s order by revision desc%s offset %s;" % ( quote( self.object_id ), limit_clause, start )
|
if start:
|
||||||
|
offset_clause = " offset %s" % start
|
||||||
|
else:
|
||||||
|
offset_clause = ""
|
||||||
|
|
||||||
|
return "select id, revision, title, contents, notebook_id, startup, deleted_from_id, rank, user_id from note_current where notebook_id = %s order by revision desc%s%s;" % ( quote( self.object_id ), limit_clause, offset_clause )
|
||||||
|
|
||||||
def sql_load_non_startup_notes( self ):
|
def sql_load_non_startup_notes( self ):
|
||||||
"""
|
"""
|
||||||
|
@ -168,7 +175,7 @@ class Notebook( Persistent ):
|
||||||
return "select id, revision, title, contents, notebook_id, startup, deleted_from_id, rank, user_id from note_current where notebook_id = %s and lower( title ) = lower( %s );" % ( quote( self.object_id ), quote( title ) )
|
return "select id, revision, title, contents, notebook_id, startup, deleted_from_id, rank, user_id from note_current where notebook_id = %s and lower( title ) = lower( %s );" % ( quote( self.object_id ), quote( title ) )
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def sql_search_notes( user_id, first_notebook_id, search_text ):
|
def sql_search_notes( user_id, first_notebook_id, search_text, database_backend ):
|
||||||
"""
|
"""
|
||||||
Return a SQL string to perform a full-text search for notes within notebooks readable by the
|
Return a SQL string to perform a full-text search for notes within notebooks readable by the
|
||||||
given user whose contents contain the given search_text. This is a case-insensitive search.
|
given user whose contents contain the given search_text. This is a case-insensitive search.
|
||||||
|
@ -176,29 +183,44 @@ class Notebook( Persistent ):
|
||||||
@type search_text: unicode
|
@type search_text: unicode
|
||||||
@param search_text: text to search for within the notes
|
@param search_text: text to search for within the notes
|
||||||
"""
|
"""
|
||||||
# strip out all search operators
|
if database_backend == Persistent.POSTGRESQL_BACKEND:
|
||||||
search_text = Notebook.SEARCH_OPERATORS.sub( u"", search_text ).strip()
|
# strip out all search operators
|
||||||
|
search_text = Notebook.SEARCH_OPERATORS.sub( u"", search_text ).strip()
|
||||||
|
|
||||||
# join all words with boolean "and" operator
|
# join all words with boolean "and" operator
|
||||||
search_text = u"&".join( Notebook.WHITESPACE_PATTERN.split( search_text ) )
|
search_text = u"&".join( Notebook.WHITESPACE_PATTERN.split( search_text ) )
|
||||||
|
|
||||||
return \
|
return \
|
||||||
"""
|
"""
|
||||||
select id, revision, title, contents, notebook_id, startup, deleted_from_id, rank, user_id, null,
|
select id, revision, title, contents, notebook_id, startup, deleted_from_id, rank, user_id, null,
|
||||||
headline( drop_html_tags( contents ), query ) as summary from (
|
headline( drop_html_tags( contents ), query ) as summary from (
|
||||||
|
select
|
||||||
|
note_current.id, note_current.revision, note_current.title, note_current.contents,
|
||||||
|
note_current.notebook_id, note_current.startup, note_current.deleted_from_id,
|
||||||
|
rank_cd( search, query ) as rank, note_current.user_id, null, query
|
||||||
|
from
|
||||||
|
note_current, user_notebook, to_tsquery( 'default', %s ) query
|
||||||
|
where
|
||||||
|
note_current.notebook_id = user_notebook.notebook_id and user_notebook.user_id = %s and
|
||||||
|
note_current.deleted_from_id is null and
|
||||||
|
query @@ search order by note_current.notebook_id = %s desc, rank desc limit 20
|
||||||
|
) as sub;
|
||||||
|
""" % ( quote( search_text ), quote( user_id ),
|
||||||
|
quote( first_notebook_id ) )
|
||||||
|
else:
|
||||||
|
# TODO: use SQLite's FTS (full text search) support instead
|
||||||
|
return \
|
||||||
|
"""
|
||||||
select
|
select
|
||||||
note_current.id, note_current.revision, note_current.title, note_current.contents,
|
note_current.*
|
||||||
note_current.notebook_id, note_current.startup, note_current.deleted_from_id,
|
|
||||||
rank_cd( search, query ) as rank, note_current.user_id, null, query
|
|
||||||
from
|
from
|
||||||
note_current, user_notebook, to_tsquery( 'default', %s ) query
|
note_current, user_notebook
|
||||||
where
|
where
|
||||||
note_current.notebook_id = user_notebook.notebook_id and user_notebook.user_id = %s and
|
note_current.notebook_id = user_notebook.notebook_id and user_notebook.user_id = %s and
|
||||||
note_current.deleted_from_id is null and
|
note_current.deleted_from_id is null and
|
||||||
query @@ search order by note_current.notebook_id = %s desc, rank desc limit 20
|
lower( note_current.contents ) like %s
|
||||||
) as sub;
|
order by note_current.notebook_id = %s desc, rank desc limit 20
|
||||||
""" % ( quote( search_text ), quote( user_id ),
|
""" % ( quote( user_id ), quote_fuzzy( search_text ), quote( first_notebook_id ) )
|
||||||
quote( first_notebook_id ) )
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def sql_search_titles( notebook_id, search_text ):
|
def sql_search_titles( notebook_id, search_text ):
|
||||||
|
@ -220,11 +242,11 @@ class Notebook( Persistent ):
|
||||||
where
|
where
|
||||||
notebook_id = %s and
|
notebook_id = %s and
|
||||||
deleted_from_id is null and
|
deleted_from_id is null and
|
||||||
title ilike %s
|
lower( title ) like %s
|
||||||
order by
|
order by
|
||||||
revision desc limit 20;
|
revision desc limit 20;
|
||||||
""" % ( quote( notebook_id ),
|
""" % ( quote( notebook_id ),
|
||||||
quote_fuzzy( search_text ) )
|
quote_fuzzy( search_text.lower() ) )
|
||||||
|
|
||||||
def sql_highest_note_rank( self ):
|
def sql_highest_note_rank( self ):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -3,6 +3,9 @@ from pytz import utc
|
||||||
|
|
||||||
|
|
||||||
class Persistent( object ):
|
class Persistent( object ):
|
||||||
|
POSTGRESQL_BACKEND = 0
|
||||||
|
SQLITE_BACKEND = 1
|
||||||
|
|
||||||
"""
|
"""
|
||||||
A persistent database object with a unique id.
|
A persistent database object with a unique id.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -2,7 +2,6 @@ import sha
|
||||||
import random
|
import random
|
||||||
from copy import copy
|
from copy import copy
|
||||||
from Persistent import Persistent, quote
|
from Persistent import Persistent, quote
|
||||||
from controller.Database import Database
|
|
||||||
|
|
||||||
|
|
||||||
class User( Persistent ):
|
class User( Persistent ):
|
||||||
|
@ -303,16 +302,16 @@ class User( Persistent ):
|
||||||
storage for all the user's notes (including past revisions) and their uploaded files. It does
|
storage for all the user's notes (including past revisions) and their uploaded files. It does
|
||||||
not include storage for the notebooks themselves.
|
not include storage for the notebooks themselves.
|
||||||
"""
|
"""
|
||||||
if database_backend == Database.POSTGRESQL_BACKEND:
|
if database_backend == Persistent.POSTGRESQL_BACKEND:
|
||||||
# this counts bytes for the contents of each column
|
# this counts bytes for the contents of each column
|
||||||
note_size_clause = "pg_column_size( note.* )"
|
note_size_clause = "pg_column_size( note.* )"
|
||||||
else:
|
else:
|
||||||
# this isn't perfect, because length() counts UTF-8 characters instead of bytes
|
# this isn't perfect, because length() counts UTF-8 characters instead of bytes.
|
||||||
|
# some columns are left out because they can be null, which screws up the addition
|
||||||
note_size_clause = \
|
note_size_clause = \
|
||||||
"""
|
"""
|
||||||
length( note.id ) + length( note.revision ) + length( note.title ) + length( note.contents ) +
|
length( note.id ) + length( note.revision ) + length( note.title ) + length( note.contents ) +
|
||||||
length( note.notebook_id ) + length( note.startup ) + length( note.deleted_from_id ) +
|
length( note.notebook_id ) + length( note.startup ) + length( note.user_id )
|
||||||
length( note.rank ) + length( note.search ) + length( note.user_id )
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return \
|
return \
|
||||||
|
|
Reference in New Issue