salsa/common_patterns/selection.html
2020-06-24 16:02:21 +00:00

312 lines
16 KiB
HTML

<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Selection - Salsa</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="shortcut icon" href="../favicon.png">
<link rel="stylesheet" href="../css/variables.css">
<link rel="stylesheet" href="../css/general.css">
<link rel="stylesheet" href="../css/chrome.css">
<link rel="stylesheet" href="../css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="../FontAwesome/css/font-awesome.css">
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,700italic,800italic,400,300,600,700,800" rel="stylesheet" type="text/css">
<link href="https://fonts.googleapis.com/css?family=Source+Code+Pro:500" rel="stylesheet" type="text/css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="../highlight.css">
<link rel="stylesheet" href="../tomorrow-night.css">
<link rel="stylesheet" href="../ayu-highlight.css">
<!-- Custom theme stylesheets -->
<link rel="stylesheet" href="../mermaid.css">
</head>
<body class="light">
<!-- Provide site root to javascript -->
<script type="text/javascript">
var path_to_root = "../";
var default_theme = "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
document.body.className = theme;
document.querySelector('html').className = theme + ' js';
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li><a href="../about_salsa.html"><strong aria-hidden="true">1.</strong> About salsa</a></li><li><a href="../how_to_use.html"><strong aria-hidden="true">2.</strong> How to use Salsa</a></li><li><a href="../how_salsa_works.html"><strong aria-hidden="true">3.</strong> How Salsa works</a></li><li><a href="../common_patterns.html"><strong aria-hidden="true">4.</strong> Common patterns</a></li><li><ol class="section"><li><a href="../common_patterns/selection.html" class="active"><strong aria-hidden="true">4.1.</strong> Selection</a></li><li><a href="../common_patterns/on_demand_inputs.html"><strong aria-hidden="true">4.2.</strong> On-demand (Lazy) inputs</a></li></ol></li><li><a href="../videos.html"><strong aria-hidden="true">5.</strong> YouTube videos</a></li><li><a href="../plumbing.html"><strong aria-hidden="true">6.</strong> Plumbing</a></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar" class="menu-bar">
<div id="menu-bar-sticky-container">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Salsa</h1>
<div class="right-buttons">
<a href="../print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1><a class="header" href="#selection" id="selection">Selection</a></h1>
<p>The &quot;selection&quot; (or &quot;firewall&quot;) pattern is when you have a query Qsel that reads from some
other Qbase and extracts some small bit of information from Qbase that it returns.
In particular, Qsel does not combine values from other queries. In some sense,
then, Qsel is redundant -- you could have just extracted the information
the information from Qbase yourself, and done without the salsa machinery. But
Qsel serves a role in that it limits the amount of re-execution that is required
when Qbase changes.</p>
<h2><a class="header" href="#example-the-base-query" id="example-the-base-query">Example: the base query</a></h2>
<p>For example, imagine that you have a query <code>parse</code> that parses the input text of a request
and returns a <code>ParsedResult</code>, which contains a header and a body:</p>
<pre><code class="language-rust ignore">#[derive(Clone, Debug, PartialEq, Eq)]
struct ParsedResult {
header: Vec&lt;ParsedHeader&gt;,
body: String,
}
#[derive(Clone, Debug, PartialEq, Eq)]
struct ParsedHeader {
key: String,
value: String,
}
#[salsa::query_group(Request)]
trait RequestParser {
/// The base text of the request.
#[salsa::input]
fn request_text(&amp;self) -&gt; String;
/// The parsed form of the request.
fn parse(&amp;self) -&gt; ParsedResult;
}
</code></pre>
<h2><a class="header" href="#example-a-selecting-query" id="example-a-selecting-query">Example: a selecting query</a></h2>
<p>And now you have a number of derived queries that only look at the header.
For example, one might extract the &quot;content-type' header:</p>
<pre><code class="language-rust ignore">#[salsa::query_group(Request)]
trait RequestUtil: RequestParser {
fn content_type(&amp;self) -&gt; Option&lt;String&gt;;
}
fn content_type(db: &amp;impl RequestUtil) -&gt; Option&lt;String&gt; {
db.parse()
.header
.iter()
.find(|header| header.key == &quot;content-type&quot;)
.map(|header| header.value.clone())
}
</code></pre>
<h2><a class="header" href="#why-prefer-a-selecting-query" id="why-prefer-a-selecting-query">Why prefer a selecting query?</a></h2>
<p>This <code>content_type</code> query is an instance of the <em>selection</em> pattern. It only
&quot;selects&quot; a small bit of information from the <code>ParsedResult</code>. You might not have
made it a query at all, but instead made it a method on <code>ParsedResult</code>.</p>
<p>But using a query for <code>content_type</code> has an advantage: now if there are downstream
queries that only depend on the <code>content_type</code> (or perhaps on other headers extracted
via a similar pattern), those queries will not have to be re-executed when the request
changes <em>unless</em> the content-type header changes. Consider the dependency graph:</p>
<pre><code class="language-text">request_text --&gt; parse --&gt; content_type --&gt; (other queries)
</code></pre>
<p>When the <code>request_text</code> changes, we are always going to have to re-execute <code>parse</code>.
If that produces a new parsed result, we are <em>also</em> going to re-execute <code>content_type</code>.
But if the result of <code>content_type</code> has not changed, then we will <em>not</em> re-execute
the other queries.</p>
<h2><a class="header" href="#more-levels-of-selection" id="more-levels-of-selection">More levels of selection</a></h2>
<p>In fact, in our example we might consider introducing another level of selection.
Instead of having <code>content_type</code> directly access the results of <code>parse</code>, it might be better
to insert a selecting query that just extracts the header:</p>
<pre><code class="language-rust ignore">#[salsa::query_group(Request)]
trait RequestUtil: RequestParser {
fn header(&amp;self) -&gt; Vec&lt;ParsedHeader&gt;;
fn content_type(&amp;self) -&gt; Option&lt;String&gt;;
}
fn header(db: &amp;impl RequestUtil) -&gt; Vec&lt;ParsedHeader&gt; {
db.parse().header.clone()
}
fn content_type(db: &amp;impl RequestUtil) -&gt; Option&lt;String&gt; {
db.header()
.iter()
.find(|header| header.key == &quot;content-type&quot;)
.map(|header| header.value.clone())
}
</code></pre>
<p>This will result in a dependency graph like so:</p>
<pre><code class="language-text">request_text --&gt; parse --&gt; header --&gt; content_type --&gt; (other queries)
</code></pre>
<p>The advantage of this is that changes that only effect the &quot;body&quot; or
only consume small parts of the request will
not require us to re-execute <code>content_type</code> at all. This would be particularly
valuable if there are a lot of dependent headers.</p>
<h2><a class="header" href="#a-note-on-cloning-and-efficiency" id="a-note-on-cloning-and-efficiency">A note on cloning and efficiency</a></h2>
<p>In this example, we used common Rust types like <code>Vec</code> and <code>String</code>,
and we cloned them quite frequently. This will work just fine in Salsa,
but it may not be the most efficient choice. This is because each clone
is going to produce a deep copy of the result. As a simple fix, you
might convert your data structures to use <code>Arc</code> (e.g., <code>Arc&lt;Vec&lt;ParsedHeader&gt;&gt;</code>),
which makes cloning cheap.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../common_patterns.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next" href="../common_patterns/on_demand_inputs.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a href="../common_patterns.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a href="../common_patterns/on_demand_inputs.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script src="../elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="../clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="../book.js" type="text/javascript" charset="utf-8"></script>
<!-- Custom JS scripts -->
<script type="text/javascript" src="../mermaid.min.js"></script>
<script type="text/javascript" src="../mermaid-init.js"></script>
</body>
</html>